Move cycle counting to CPU Core
This isn't accurate, I'm probably missing something but at least it'll make the instruction implementation more clean for now.. Former-commit-id: de24b15e1a51e1998207e5ea96fc8543f2553a26
This commit is contained in:
parent
f8ebe26e5e
commit
be9499c76d
|
@ -6,30 +6,22 @@ use crate::arm7tdmi::exception::Exception;
|
||||||
use crate::arm7tdmi::psr::RegPSR;
|
use crate::arm7tdmi::psr::RegPSR;
|
||||||
use crate::arm7tdmi::{Addr, CpuError, CpuResult, CpuState, DecodedInstruction, REG_PC};
|
use crate::arm7tdmi::{Addr, CpuError, CpuResult, CpuState, DecodedInstruction, REG_PC};
|
||||||
|
|
||||||
use crate::sysbus::SysBus;
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
ArmFormat, ArmInstruction, ArmOpCode, ArmRegisterShift, ArmShiftType, ArmShiftedValue,
|
ArmFormat, ArmInstruction, ArmOpCode, ArmRegisterShift, ArmShiftType, ArmShiftedValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Core {
|
impl Core {
|
||||||
pub fn exec_arm(&mut self, sysbus: &mut Bus, insn: ArmInstruction) -> CpuExecResult {
|
pub fn exec_arm(&mut self, bus: &mut Bus, insn: ArmInstruction) -> CpuExecResult {
|
||||||
if !self.check_arm_cond(insn.cond) {
|
if !self.check_arm_cond(insn.cond) {
|
||||||
self.add_cycles(
|
|
||||||
insn.pc + (self.word_size() as u32),
|
|
||||||
sysbus,
|
|
||||||
Seq + MemoryAccess32,
|
|
||||||
);
|
|
||||||
self.add_cycle();
|
|
||||||
return Ok(CpuPipelineAction::IncPC);
|
return Ok(CpuPipelineAction::IncPC);
|
||||||
}
|
}
|
||||||
match insn.fmt {
|
match insn.fmt {
|
||||||
ArmFormat::BX => self.exec_bx(sysbus, insn),
|
ArmFormat::BX => self.exec_bx(bus, insn),
|
||||||
ArmFormat::B_BL => self.exec_b_bl(sysbus, insn),
|
ArmFormat::B_BL => self.exec_b_bl(bus, insn),
|
||||||
ArmFormat::DP => self.exec_data_processing(sysbus, insn),
|
ArmFormat::DP => self.exec_data_processing(bus, insn),
|
||||||
ArmFormat::SWI => self.exec_swi(sysbus, insn),
|
ArmFormat::SWI => self.exec_swi(bus, insn),
|
||||||
ArmFormat::LDR_STR => self.exec_ldr_str(sysbus, insn),
|
ArmFormat::LDR_STR => self.exec_ldr_str(bus, insn),
|
||||||
ArmFormat::MSR_REG => self.exec_msr_reg(sysbus, insn),
|
ArmFormat::MSR_REG => self.exec_msr_reg(bus, insn),
|
||||||
_ => Err(CpuError::UnimplementedCpuInstruction(
|
_ => Err(CpuError::UnimplementedCpuInstruction(
|
||||||
insn.pc,
|
insn.pc,
|
||||||
insn.raw,
|
insn.raw,
|
||||||
|
@ -39,33 +31,18 @@ impl Core {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cycles 2S+1N
|
/// Cycles 2S+1N
|
||||||
fn exec_b_bl(
|
fn exec_b_bl(&mut self, bus: &mut Bus, insn: ArmInstruction) -> CpuResult<CpuPipelineAction> {
|
||||||
&mut self,
|
|
||||||
sysbus: &mut Bus,
|
|
||||||
insn: ArmInstruction,
|
|
||||||
) -> CpuResult<CpuPipelineAction> {
|
|
||||||
if insn.link_flag() {
|
if insn.link_flag() {
|
||||||
self.set_reg(14, (insn.pc + (self.word_size() as u32)) & !0b1);
|
self.set_reg(14, (insn.pc + (self.word_size() as u32)) & !0b1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// +1N
|
|
||||||
self.add_cycles(self.pc, sysbus, NonSeq + MemoryAccess32);
|
|
||||||
|
|
||||||
self.pc = (self.pc as i32).wrapping_add(insn.branch_offset()) as u32 & !1;
|
self.pc = (self.pc as i32).wrapping_add(insn.branch_offset()) as u32 & !1;
|
||||||
|
|
||||||
// +2S
|
|
||||||
self.add_cycles(self.pc, sysbus, Seq + MemoryAccess32);
|
|
||||||
self.add_cycles(
|
|
||||||
self.pc + (self.word_size() as u32),
|
|
||||||
sysbus,
|
|
||||||
Seq + MemoryAccess32,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(CpuPipelineAction::Flush)
|
Ok(CpuPipelineAction::Flush)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cycles 2S+1N
|
/// Cycles 2S+1N
|
||||||
fn exec_bx(&mut self, sysbus: &mut Bus, insn: ArmInstruction) -> CpuResult<CpuPipelineAction> {
|
fn exec_bx(&mut self, bus: &mut Bus, insn: ArmInstruction) -> CpuResult<CpuPipelineAction> {
|
||||||
let rn = self.get_reg(insn.rn());
|
let rn = self.get_reg(insn.rn());
|
||||||
if rn.bit(0) {
|
if rn.bit(0) {
|
||||||
self.cpsr.set_state(CpuState::THUMB);
|
self.cpsr.set_state(CpuState::THUMB);
|
||||||
|
@ -73,34 +50,19 @@ impl Core {
|
||||||
self.cpsr.set_state(CpuState::ARM);
|
self.cpsr.set_state(CpuState::ARM);
|
||||||
}
|
}
|
||||||
|
|
||||||
// +1N
|
|
||||||
self.add_cycles(self.pc, sysbus, NonSeq + MemoryAccess32);
|
|
||||||
|
|
||||||
self.pc = rn & !1;
|
self.pc = rn & !1;
|
||||||
|
|
||||||
// +2S
|
|
||||||
self.add_cycles(self.pc, sysbus, Seq + MemoryAccess32);
|
|
||||||
self.add_cycles(
|
|
||||||
self.pc + (self.word_size() as u32),
|
|
||||||
sysbus,
|
|
||||||
Seq + MemoryAccess32,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(CpuPipelineAction::Flush)
|
Ok(CpuPipelineAction::Flush)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec_swi(
|
fn exec_swi(&mut self, _bus: &mut Bus, _insn: ArmInstruction) -> CpuResult<CpuPipelineAction> {
|
||||||
&mut self,
|
|
||||||
_sysbus: &mut Bus,
|
|
||||||
_insn: ArmInstruction,
|
|
||||||
) -> CpuResult<CpuPipelineAction> {
|
|
||||||
self.exception(Exception::SoftwareInterrupt);
|
self.exception(Exception::SoftwareInterrupt);
|
||||||
Ok(CpuPipelineAction::Flush)
|
Ok(CpuPipelineAction::Flush)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec_msr_reg(
|
fn exec_msr_reg(
|
||||||
&mut self,
|
&mut self,
|
||||||
sysbus: &mut Bus,
|
bus: &mut Bus,
|
||||||
insn: ArmInstruction,
|
insn: ArmInstruction,
|
||||||
) -> CpuResult<CpuPipelineAction> {
|
) -> CpuResult<CpuPipelineAction> {
|
||||||
let new_psr = RegPSR::new(self.get_reg(insn.rm()));
|
let new_psr = RegPSR::new(self.get_reg(insn.rm()));
|
||||||
|
@ -117,7 +79,6 @@ impl Core {
|
||||||
}
|
}
|
||||||
self.cpsr = new_psr;
|
self.cpsr = new_psr;
|
||||||
}
|
}
|
||||||
self.add_cycles(insn.pc, sysbus, Seq + MemoryAccess32);
|
|
||||||
Ok(CpuPipelineAction::IncPC)
|
Ok(CpuPipelineAction::IncPC)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +165,7 @@ impl Core {
|
||||||
/// Add x=1I cycles if Op2 shifted-by-register. Add y=1S+1N cycles if Rd=R15.
|
/// Add x=1I cycles if Op2 shifted-by-register. Add y=1S+1N cycles if Rd=R15.
|
||||||
fn exec_data_processing(
|
fn exec_data_processing(
|
||||||
&mut self,
|
&mut self,
|
||||||
sysbus: &mut Bus,
|
bus: &mut Bus,
|
||||||
insn: ArmInstruction,
|
insn: ArmInstruction,
|
||||||
) -> CpuResult<CpuPipelineAction> {
|
) -> CpuResult<CpuPipelineAction> {
|
||||||
// TODO handle carry flag
|
// TODO handle carry flag
|
||||||
|
@ -219,10 +180,6 @@ impl Core {
|
||||||
let op2 = insn.operand2()?;
|
let op2 = insn.operand2()?;
|
||||||
|
|
||||||
let rd = insn.rd();
|
let rd = insn.rd();
|
||||||
if rd == REG_PC {
|
|
||||||
// +1N
|
|
||||||
self.add_cycles(self.pc, sysbus, NonSeq + MemoryAccess32);
|
|
||||||
}
|
|
||||||
|
|
||||||
let op2: i32 = match op2 {
|
let op2: i32 = match op2 {
|
||||||
ArmShiftedValue::RotatedImmediate(immediate, rotate) => {
|
ArmShiftedValue::RotatedImmediate(immediate, rotate) => {
|
||||||
|
@ -246,17 +203,9 @@ impl Core {
|
||||||
self.set_reg(rd, result as u32);
|
self.set_reg(rd, result as u32);
|
||||||
if rd == REG_PC {
|
if rd == REG_PC {
|
||||||
pipeline_action = CpuPipelineAction::Flush;
|
pipeline_action = CpuPipelineAction::Flush;
|
||||||
// +1S
|
|
||||||
self.add_cycles(self.pc, sysbus, Seq + MemoryAccess32);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// +1S
|
|
||||||
self.add_cycles(
|
|
||||||
self.pc + (self.word_size() as u32),
|
|
||||||
sysbus,
|
|
||||||
Seq + MemoryAccess32,
|
|
||||||
);
|
|
||||||
Ok(pipeline_action)
|
Ok(pipeline_action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +238,7 @@ impl Core {
|
||||||
/// For LDR, add y=1S+1N if Rd=R15.
|
/// For LDR, add y=1S+1N if Rd=R15.
|
||||||
fn exec_ldr_str(
|
fn exec_ldr_str(
|
||||||
&mut self,
|
&mut self,
|
||||||
sysbus: &mut Bus,
|
bus: &mut Bus,
|
||||||
insn: ArmInstruction,
|
insn: ArmInstruction,
|
||||||
) -> CpuResult<CpuPipelineAction> {
|
) -> CpuResult<CpuPipelineAction> {
|
||||||
if insn.write_back_flag() && insn.rd() == insn.rn() {
|
if insn.write_back_flag() && insn.rd() == insn.rn() {
|
||||||
|
@ -316,19 +265,11 @@ impl Core {
|
||||||
if insn.load_flag() {
|
if insn.load_flag() {
|
||||||
let data = if insn.transfer_size() == 1 {
|
let data = if insn.transfer_size() == 1 {
|
||||||
// +1N
|
// +1N
|
||||||
self.add_cycles(addr, sysbus, NonSeq + MemoryAccess8);
|
self.load_8(addr, bus) as u32
|
||||||
sysbus.read_8(addr) as u32
|
|
||||||
} else {
|
} else {
|
||||||
// +1N
|
// +1N
|
||||||
self.add_cycles(addr, sysbus, NonSeq + MemoryAccess32);
|
self.load_32(addr, bus)
|
||||||
sysbus.read_32(addr)
|
|
||||||
};
|
};
|
||||||
// +1S
|
|
||||||
self.add_cycles(
|
|
||||||
self.pc + (self.word_size() as u32),
|
|
||||||
sysbus,
|
|
||||||
Seq + MemoryAccess32,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.set_reg(insn.rd(), data);
|
self.set_reg(insn.rd(), data);
|
||||||
|
|
||||||
|
@ -336,27 +277,15 @@ impl Core {
|
||||||
self.add_cycle();
|
self.add_cycle();
|
||||||
// +y
|
// +y
|
||||||
if insn.rd() == REG_PC {
|
if insn.rd() == REG_PC {
|
||||||
// +1S
|
|
||||||
self.add_cycles(self.pc, sysbus, Seq + MemoryAccess32);
|
|
||||||
// +1N
|
|
||||||
self.add_cycles(
|
|
||||||
self.pc + (self.word_size() as u32),
|
|
||||||
sysbus,
|
|
||||||
NonSeq + MemoryAccess32,
|
|
||||||
);
|
|
||||||
pipeline_action = CpuPipelineAction::Flush;
|
pipeline_action = CpuPipelineAction::Flush;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.add_cycles(addr, sysbus, NonSeq + MemoryAccess32);
|
|
||||||
let value = self.get_reg(insn.rd());
|
let value = self.get_reg(insn.rd());
|
||||||
if insn.transfer_size() == 1 {
|
if insn.transfer_size() == 1 {
|
||||||
// +1N
|
self.store_8(addr, value as u8, bus);
|
||||||
self.add_cycles(dest, sysbus, NonSeq + MemoryAccess8);
|
|
||||||
sysbus.write_8(addr, value as u8).expect("bus error");
|
|
||||||
} else {
|
} else {
|
||||||
// +1N
|
// +1N
|
||||||
self.add_cycles(dest, sysbus, NonSeq + MemoryAccess32);
|
self.store_32(addr, value, bus);
|
||||||
sysbus.write_32(addr, value).expect("bus error");
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -538,17 +538,17 @@ mod tests {
|
||||||
core.set_reg(REG_SP, 0);
|
core.set_reg(REG_SP, 0);
|
||||||
|
|
||||||
let bytes = vec![
|
let bytes = vec![
|
||||||
/* 0: */ 0xaa, 0xbb, 0xcc, 0xdd,
|
/* 0: */ 0xaa, 0xbb, 0xcc, 0xdd, /* 4: */ 0xaa, 0xbb, 0xcc, 0xdd,
|
||||||
/* 4: */ 0xaa, 0xbb, 0xcc, 0xdd,
|
/* 8: */ 0xaa, 0xbb, 0xcc, 0xdd, /* c: */ 0xaa, 0xbb, 0xcc, 0xdd,
|
||||||
/* 8: */ 0xaa, 0xbb, 0xcc, 0xdd,
|
|
||||||
/* c: */ 0xaa, 0xbb, 0xcc, 0xdd,
|
|
||||||
/* 10: */ 0xaa, 0xbb, 0xcc, 0xdd,
|
/* 10: */ 0xaa, 0xbb, 0xcc, 0xdd,
|
||||||
];
|
];
|
||||||
let mut mem = BoxedMemory::new(bytes.into_boxed_slice());
|
let mut mem = BoxedMemory::new(bytes.into_boxed_slice());
|
||||||
|
|
||||||
assert_ne!(mem.read_32(core.get_reg(REG_SP) + 0x10), 0x12345678);
|
assert_ne!(mem.read_32(core.get_reg(REG_SP) + 0x10), 0x12345678);
|
||||||
assert_eq!(core.exec_arm(&mut mem, decoded), Ok(CpuPipelineAction::IncPC));
|
assert_eq!(
|
||||||
|
core.exec_arm(&mut mem, decoded),
|
||||||
|
Ok(CpuPipelineAction::IncPC)
|
||||||
|
);
|
||||||
assert_eq!(mem.read_32(core.get_reg(REG_SP) + 0x10), 0x12345678);
|
assert_eq!(mem.read_32(core.get_reg(REG_SP) + 0x10), 0x12345678);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,31 @@
|
||||||
use super::Addr;
|
use std::fmt;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
use super::Addr;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum MemoryAccessType {
|
pub enum MemoryAccessType {
|
||||||
NonSeq,
|
NonSeq,
|
||||||
Seq,
|
Seq,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for MemoryAccessType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
MemoryAccessType::NonSeq => "N",
|
||||||
|
MemoryAccessType::Seq => "S",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum MemoryAccessWidth {
|
pub enum MemoryAccessWidth {
|
||||||
MemoryAccess8,
|
MemoryAccess8,
|
||||||
MemoryAccess16,
|
MemoryAccess16,
|
||||||
|
@ -23,8 +40,15 @@ impl Add<MemoryAccessWidth> for MemoryAccessType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct MemoryAccess(pub MemoryAccessType, pub MemoryAccessWidth);
|
pub struct MemoryAccess(pub MemoryAccessType, pub MemoryAccessWidth);
|
||||||
|
|
||||||
|
impl fmt::Display for MemoryAccess {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}-Cycle ({:?})", self.0, self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Bus {
|
pub trait Bus {
|
||||||
fn read_32(&self, addr: Addr) -> u32 {
|
fn read_32(&self, addr: Addr) -> u32 {
|
||||||
self.get_bytes(addr).read_u32::<LittleEndian>().unwrap()
|
self.get_bytes(addr).read_u32::<LittleEndian>().unwrap()
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::Add;
|
|
||||||
|
|
||||||
use ansi_term::{Colour, Style};
|
use ansi_term::{Colour, Style};
|
||||||
use num_traits::Num;
|
use num_traits::Num;
|
||||||
|
|
||||||
use crate::sysbus::SysBus;
|
|
||||||
|
|
||||||
pub use super::exception::Exception;
|
pub use super::exception::Exception;
|
||||||
use super::{
|
use super::{
|
||||||
arm::*,
|
arm::*,
|
||||||
bus::{Bus, MemoryAccess, MemoryAccessType::*, MemoryAccessWidth::*},
|
bus::{Bus, MemoryAccess, MemoryAccessType, MemoryAccessType::*, MemoryAccessWidth::*},
|
||||||
psr::RegPSR,
|
psr::RegPSR,
|
||||||
reg_string,
|
reg_string,
|
||||||
thumb::ThumbInstruction,
|
thumb::ThumbInstruction,
|
||||||
|
@ -84,6 +80,8 @@ pub struct Core {
|
||||||
// store the gpr before executing an instruction to show diff in the Display impl
|
// store the gpr before executing an instruction to show diff in the Display impl
|
||||||
gpr_previous: [u32; 15],
|
gpr_previous: [u32; 15],
|
||||||
|
|
||||||
|
memreq: Addr,
|
||||||
|
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,6 +96,7 @@ pub type CpuExecResult = CpuResult<CpuPipelineAction>;
|
||||||
impl Core {
|
impl Core {
|
||||||
pub fn new() -> Core {
|
pub fn new() -> Core {
|
||||||
Core {
|
Core {
|
||||||
|
memreq: 0xffff_0000, // set memreq to an invalid addr so the first load cycle will be non-sequential
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,8 +156,10 @@ impl Core {
|
||||||
}
|
}
|
||||||
self.map_banked_registers(curr_mode, new_mode);
|
self.map_banked_registers(curr_mode, new_mode);
|
||||||
let next_index = new_mode.bank_index();
|
let next_index = new_mode.bank_index();
|
||||||
self.gpr_banked_r14[next_index] = self.pc.wrapping_sub(self.word_size() as u32).wrapping_add(4);
|
self.gpr_banked_r14[next_index] = self
|
||||||
|
.pc
|
||||||
|
.wrapping_sub(self.word_size() as u32)
|
||||||
|
.wrapping_add(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resets the cpu
|
/// Resets the cpu
|
||||||
|
@ -182,11 +183,62 @@ impl Core {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_cycle(&mut self) {
|
pub fn add_cycle(&mut self) {
|
||||||
|
println!("<cycle I-Cyclel> total: {}", self.cycles);
|
||||||
self.cycles += 1;
|
self.cycles += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_cycles(&mut self, addr: Addr, sysbus: &Bus, access: MemoryAccess) {
|
pub fn add_cycles(&mut self, addr: Addr, bus: &Bus, access: MemoryAccess) {
|
||||||
self.cycles += sysbus.get_cycles(addr, access);
|
println!("<cycle {:#x} {}> total: {}", addr, access, self.cycles);
|
||||||
|
self.cycles += bus.get_cycles(addr, access);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cycle_type(&self, addr: Addr) -> MemoryAccessType {
|
||||||
|
if addr == self.memreq || addr == self.memreq + (self.word_size() as Addr) {
|
||||||
|
Seq
|
||||||
|
} else {
|
||||||
|
NonSeq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_32(&mut self, addr: Addr, bus: &mut Bus) -> u32 {
|
||||||
|
self.add_cycles(addr, bus, self.cycle_type(addr) + MemoryAccess32);
|
||||||
|
self.memreq = addr;
|
||||||
|
bus.read_32(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_16(&mut self, addr: Addr, bus: &mut Bus) -> u16 {
|
||||||
|
let cycle_type = self.cycle_type(addr);
|
||||||
|
self.add_cycles(addr, bus, cycle_type + MemoryAccess16);
|
||||||
|
self.memreq = addr;
|
||||||
|
bus.read_16(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_8(&mut self, addr: Addr, bus: &mut Bus) -> u8 {
|
||||||
|
let cycle_type = self.cycle_type(addr);
|
||||||
|
self.add_cycles(addr, bus, cycle_type + MemoryAccess8);
|
||||||
|
self.memreq = addr;
|
||||||
|
bus.read_8(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store_32(&mut self, addr: Addr, value: u32, bus: &mut Bus) {
|
||||||
|
let cycle_type = self.cycle_type(addr);
|
||||||
|
self.add_cycles(addr, bus, cycle_type + MemoryAccess32);
|
||||||
|
self.memreq = addr;
|
||||||
|
bus.write_32(addr, value).expect("store_32 error");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store_16(&mut self, addr: Addr, value: u16, bus: &mut Bus) {
|
||||||
|
let cycle_type = self.cycle_type(addr);
|
||||||
|
self.add_cycles(addr, bus, cycle_type + MemoryAccess16);
|
||||||
|
self.memreq = addr;
|
||||||
|
bus.write_16(addr, value).expect("store_16 error");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store_8(&mut self, addr: Addr, value: u8, bus: &mut Bus) {
|
||||||
|
let cycle_type = self.cycle_type(addr);
|
||||||
|
self.add_cycles(addr, bus, cycle_type + MemoryAccess8);
|
||||||
|
self.memreq = addr;
|
||||||
|
bus.write_8(addr, value).expect("store_16 error");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_arm_cond(&self, cond: ArmCond) -> bool {
|
pub fn check_arm_cond(&self, cond: ArmCond) -> bool {
|
||||||
|
@ -212,10 +264,11 @@ impl Core {
|
||||||
|
|
||||||
fn step_thumb(
|
fn step_thumb(
|
||||||
&mut self,
|
&mut self,
|
||||||
sysbus: &mut Bus,
|
bus: &mut Bus,
|
||||||
) -> CpuResult<(Option<DecodedInstruction>, CpuPipelineAction)> {
|
) -> CpuResult<(Option<DecodedInstruction>, CpuPipelineAction)> {
|
||||||
// fetch
|
// fetch
|
||||||
let new_fetched = sysbus.read_16(self.pc);
|
// let new_fetched = bus.read_16(self.pc);
|
||||||
|
let new_fetched = self.load_16(self.pc, bus);
|
||||||
|
|
||||||
// decode
|
// decode
|
||||||
let new_decoded = match self.pipeline_thumb.fetched {
|
let new_decoded = match self.pipeline_thumb.fetched {
|
||||||
|
@ -230,7 +283,7 @@ impl Core {
|
||||||
let result = match self.pipeline_thumb.decoded {
|
let result = match self.pipeline_thumb.decoded {
|
||||||
Some(d) => {
|
Some(d) => {
|
||||||
self.gpr_previous = self.get_registers();
|
self.gpr_previous = self.get_registers();
|
||||||
let action = self.exec_thumb(sysbus, d)?;
|
let action = self.exec_thumb(bus, d)?;
|
||||||
Ok((Some(DecodedInstruction::Thumb(d)), action))
|
Ok((Some(DecodedInstruction::Thumb(d)), action))
|
||||||
}
|
}
|
||||||
None => Ok((None, CpuPipelineAction::IncPC)),
|
None => Ok((None, CpuPipelineAction::IncPC)),
|
||||||
|
@ -246,10 +299,10 @@ impl Core {
|
||||||
|
|
||||||
fn step_arm(
|
fn step_arm(
|
||||||
&mut self,
|
&mut self,
|
||||||
sysbus: &mut Bus,
|
bus: &mut Bus,
|
||||||
) -> CpuResult<(Option<DecodedInstruction>, CpuPipelineAction)> {
|
) -> CpuResult<(Option<DecodedInstruction>, CpuPipelineAction)> {
|
||||||
// fetch
|
// let new_fetched = bus.read_32(self.pc);
|
||||||
let new_fetched = sysbus.read_32(self.pc);
|
let new_fetched = self.load_32(self.pc, bus);
|
||||||
|
|
||||||
// decode
|
// decode
|
||||||
let new_decoded = match self.pipeline_arm.fetched {
|
let new_decoded = match self.pipeline_arm.fetched {
|
||||||
|
@ -264,7 +317,7 @@ impl Core {
|
||||||
let result = match self.pipeline_arm.decoded {
|
let result = match self.pipeline_arm.decoded {
|
||||||
Some(d) => {
|
Some(d) => {
|
||||||
self.gpr_previous = self.get_registers();
|
self.gpr_previous = self.get_registers();
|
||||||
let action = self.exec_arm(sysbus, d)?;
|
let action = self.exec_arm(bus, d)?;
|
||||||
Ok((Some(DecodedInstruction::Arm(d)), action))
|
Ok((Some(DecodedInstruction::Arm(d)), action))
|
||||||
}
|
}
|
||||||
None => Ok((None, CpuPipelineAction::IncPC)),
|
None => Ok((None, CpuPipelineAction::IncPC)),
|
||||||
|
@ -280,10 +333,10 @@ impl Core {
|
||||||
|
|
||||||
/// Perform a pipeline step
|
/// Perform a pipeline step
|
||||||
/// If an instruction was executed in this step, return it.
|
/// If an instruction was executed in this step, return it.
|
||||||
pub fn step(&mut self, sysbus: &mut Bus) -> CpuResult<Option<DecodedInstruction>> {
|
pub fn step(&mut self, bus: &mut Bus) -> CpuResult<Option<DecodedInstruction>> {
|
||||||
let (executed_instruction, pipeline_action) = match self.cpsr.state() {
|
let (executed_instruction, pipeline_action) = match self.cpsr.state() {
|
||||||
CpuState::ARM => self.step_arm(sysbus),
|
CpuState::ARM => self.step_arm(bus),
|
||||||
CpuState::THUMB => self.step_thumb(sysbus),
|
CpuState::THUMB => self.step_thumb(bus),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
match pipeline_action {
|
match pipeline_action {
|
||||||
|
@ -328,9 +381,9 @@ impl Core {
|
||||||
/// A step that returns only once an instruction was executed.
|
/// A step that returns only once an instruction was executed.
|
||||||
/// Returns the address of PC before executing an instruction,
|
/// Returns the address of PC before executing an instruction,
|
||||||
/// and the address of the next instruction to be executed;
|
/// and the address of the next instruction to be executed;
|
||||||
pub fn step_debugger(&mut self, sysbus: &mut Bus) -> CpuResult<DecodedInstruction> {
|
pub fn step_debugger(&mut self, bus: &mut Bus) -> CpuResult<DecodedInstruction> {
|
||||||
loop {
|
loop {
|
||||||
if let Some(i) = self.step(sysbus)? {
|
if let Some(i) = self.step(bus)? {
|
||||||
return Ok(i);
|
return Ok(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,11 +92,7 @@ impl ThumbInstruction {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_thumb_add_sp(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_thumb_add_sp(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(
|
write!(f, "add\tsp, #{imm:x}", imm = self.sword7())
|
||||||
f,
|
|
||||||
"add\tsp, #{imm:x}",
|
|
||||||
imm = self.sword7()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_thumb_push_pop(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt_thumb_push_pop(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
@ -110,7 +106,7 @@ impl ThumbInstruction {
|
||||||
for reg in register_list {
|
for reg in register_list {
|
||||||
has_reg = true;
|
has_reg = true;
|
||||||
write!(f, ", {}", reg_string(reg))?;
|
write!(f, ", {}", reg_string(reg))?;
|
||||||
};
|
}
|
||||||
if self.flag(ThumbInstruction::FLAG_R) {
|
if self.flag(ThumbInstruction::FLAG_R) {
|
||||||
let r = if self.is_load() { "pc" } else { "lr" };
|
let r = if self.is_load() { "pc" } else { "lr" };
|
||||||
if has_reg {
|
if has_reg {
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
use crate::arm7tdmi::arm::*;
|
use crate::arm7tdmi::arm::*;
|
||||||
use crate::arm7tdmi::bus::{Bus, MemoryAccessType::*, MemoryAccessWidth::*};
|
use crate::arm7tdmi::bus::{Bus, MemoryAccessType::*, MemoryAccessWidth::*};
|
||||||
use crate::arm7tdmi::cpu::{Core, CpuExecResult, CpuPipelineAction};
|
use crate::arm7tdmi::cpu::{Core, CpuExecResult, CpuPipelineAction};
|
||||||
use crate::arm7tdmi::{Addr, REG_PC, REG_LR, REG_SP, CpuState, reg_string};
|
use crate::arm7tdmi::{reg_string, Addr, CpuState, REG_LR, REG_PC, REG_SP};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
fn push(cpu: &mut Core, bus: &mut Bus, r: usize) {
|
fn push(cpu: &mut Core, bus: &mut Bus, r: usize) {
|
||||||
cpu.gpr[REG_SP] -= 4;
|
cpu.gpr[REG_SP] -= 4;
|
||||||
let stack_addr = cpu.gpr[REG_SP];
|
let stack_addr = cpu.gpr[REG_SP];
|
||||||
bus.write_32(stack_addr, cpu.get_reg(r)).expect("bus error");
|
cpu.store_32(stack_addr, cpu.get_reg(r), bus)
|
||||||
}
|
}
|
||||||
fn pop(cpu: &mut Core, bus: &mut Bus, r: usize) {
|
fn pop(cpu: &mut Core, bus: &mut Bus, r: usize) {
|
||||||
let stack_addr = cpu.gpr[REG_SP];
|
let stack_addr = cpu.gpr[REG_SP];
|
||||||
let val = bus.read_32(stack_addr);
|
let val = cpu.load_32(stack_addr, bus);
|
||||||
cpu.set_reg(r, val);
|
cpu.set_reg(r, val);
|
||||||
cpu.gpr[REG_SP] = stack_addr + 4;
|
cpu.gpr[REG_SP] = stack_addr + 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Core {
|
impl Core {
|
||||||
fn exec_thumb_add_sub(&mut self, bus: &mut Bus, insn: ThumbInstruction) -> CpuExecResult {
|
fn exec_thumb_add_sub(&mut self, bus: &mut Bus, insn: ThumbInstruction) -> CpuExecResult {
|
||||||
|
@ -34,12 +34,7 @@ impl Core {
|
||||||
if let Some(result) = result {
|
if let Some(result) = result {
|
||||||
self.set_reg(insn.rd(), result as u32);
|
self.set_reg(insn.rd(), result as u32);
|
||||||
}
|
}
|
||||||
// +1S
|
|
||||||
self.add_cycles(
|
|
||||||
insn.pc + (self.word_size() as u32),
|
|
||||||
bus,
|
|
||||||
Seq + MemoryAccess32,
|
|
||||||
);
|
|
||||||
Ok(CpuPipelineAction::IncPC)
|
Ok(CpuPipelineAction::IncPC)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,21 +50,12 @@ impl Core {
|
||||||
if let Some(result) = result {
|
if let Some(result) = result {
|
||||||
self.set_reg(insn.rd(), result as u32);
|
self.set_reg(insn.rd(), result as u32);
|
||||||
}
|
}
|
||||||
// +1S
|
|
||||||
self.add_cycles(
|
|
||||||
insn.pc + (self.word_size() as u32),
|
|
||||||
bus,
|
|
||||||
Seq + MemoryAccess16,
|
|
||||||
);
|
|
||||||
Ok(CpuPipelineAction::IncPC)
|
Ok(CpuPipelineAction::IncPC)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cycles 2S+1N
|
/// Cycles 2S+1N
|
||||||
fn exec_thumb_bx(
|
fn exec_thumb_bx(&mut self, bus: &mut Bus, insn: ThumbInstruction) -> CpuExecResult {
|
||||||
&mut self,
|
|
||||||
bus: &mut Bus,
|
|
||||||
insn: ThumbInstruction,
|
|
||||||
) -> CpuExecResult {
|
|
||||||
let src_reg = if insn.flag(ThumbInstruction::FLAG_H2) {
|
let src_reg = if insn.flag(ThumbInstruction::FLAG_H2) {
|
||||||
insn.rs() + 8
|
insn.rs() + 8
|
||||||
} else {
|
} else {
|
||||||
|
@ -83,19 +69,8 @@ impl Core {
|
||||||
self.cpsr.set_state(CpuState::ARM);
|
self.cpsr.set_state(CpuState::ARM);
|
||||||
}
|
}
|
||||||
|
|
||||||
// +1N
|
|
||||||
self.add_cycles(self.pc, bus, NonSeq + MemoryAccess32);
|
|
||||||
|
|
||||||
self.pc = addr & !1;
|
self.pc = addr & !1;
|
||||||
|
|
||||||
// +2S
|
|
||||||
self.add_cycles(self.pc, bus, Seq + MemoryAccess32);
|
|
||||||
self.add_cycles(
|
|
||||||
self.pc + (self.word_size() as u32),
|
|
||||||
bus,
|
|
||||||
Seq + MemoryAccess32,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(CpuPipelineAction::Flush)
|
Ok(CpuPipelineAction::Flush)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,21 +83,14 @@ impl Core {
|
||||||
self.exec_thumb_bx(bus, insn)
|
self.exec_thumb_bx(bus, insn)
|
||||||
} else {
|
} else {
|
||||||
unimplemented!("Sorry, I'm tired");
|
unimplemented!("Sorry, I'm tired");
|
||||||
Ok(CpuPipelineAction::IncPC)
|
// Ok(CpuPipelineAction::IncPC)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec_thumb_ldr_pc(&mut self, bus: &mut Bus, insn: ThumbInstruction) -> CpuExecResult {
|
fn exec_thumb_ldr_pc(&mut self, bus: &mut Bus, insn: ThumbInstruction) -> CpuExecResult {
|
||||||
let addr = (insn.pc & !0b10) + 4 + (insn.word8() as Addr);
|
let addr = (insn.pc & !0b10) + 4 + (insn.word8() as Addr);
|
||||||
let data = bus.read_32(addr);
|
let data = self.load_32(addr, bus);
|
||||||
// +1N
|
|
||||||
self.add_cycles(addr, bus, NonSeq + MemoryAccess32);
|
|
||||||
// +1S
|
|
||||||
self.add_cycles(
|
|
||||||
insn.pc + (self.word_size() as u32),
|
|
||||||
bus,
|
|
||||||
Seq + MemoryAccess16,
|
|
||||||
);
|
|
||||||
self.set_reg(insn.rd(), data);
|
self.set_reg(insn.rd(), data);
|
||||||
// +1I
|
// +1I
|
||||||
self.add_cycle();
|
self.add_cycle();
|
||||||
|
@ -140,37 +108,21 @@ impl Core {
|
||||||
.wrapping_add(self.get_reg(insn.ro()));
|
.wrapping_add(self.get_reg(insn.ro()));
|
||||||
if insn.is_load() {
|
if insn.is_load() {
|
||||||
let data = if insn.is_transfering_bytes() {
|
let data = if insn.is_transfering_bytes() {
|
||||||
// +1N
|
self.load_8(addr, bus) as u32
|
||||||
self.add_cycles(addr, bus, NonSeq + MemoryAccess8);
|
|
||||||
bus.read_8(addr) as u32
|
|
||||||
} else {
|
} else {
|
||||||
// +1N
|
self.load_32(addr, bus)
|
||||||
self.add_cycles(addr, bus, NonSeq + MemoryAccess32);
|
|
||||||
bus.read_32(addr)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// +1S
|
|
||||||
self.add_cycles(
|
|
||||||
insn.pc + (self.word_size() as u32),
|
|
||||||
bus,
|
|
||||||
Seq + MemoryAccess32,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.set_reg(insn.rd(), data);
|
self.set_reg(insn.rd(), data);
|
||||||
|
|
||||||
// +1I
|
// +1I
|
||||||
self.add_cycle();
|
self.add_cycle();
|
||||||
} else {
|
} else {
|
||||||
self.add_cycles(addr, bus, NonSeq + MemoryAccess32);
|
|
||||||
let value = self.get_reg(insn.rd());
|
let value = self.get_reg(insn.rd());
|
||||||
if insn.is_transfering_bytes() {
|
if insn.is_transfering_bytes() {
|
||||||
// +1N
|
self.store_8(addr, value as u8, bus);
|
||||||
self.add_cycles(addr, bus, NonSeq + MemoryAccess8);
|
|
||||||
bus.write_8(addr, value as u8).expect("bus error");
|
|
||||||
} else {
|
} else {
|
||||||
// +1N
|
self.store_32(addr, value, bus);
|
||||||
self.add_cycles(addr, bus, NonSeq + MemoryAccess32);
|
|
||||||
bus.write_32(addr, value).expect("bus error");
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,27 +138,16 @@ impl Core {
|
||||||
if let Some(result) = result {
|
if let Some(result) = result {
|
||||||
self.gpr[REG_SP] = result as u32;
|
self.gpr[REG_SP] = result as u32;
|
||||||
}
|
}
|
||||||
// +1S
|
|
||||||
self.add_cycles(
|
|
||||||
insn.pc + (self.word_size() as u32),
|
|
||||||
bus,
|
|
||||||
Seq + MemoryAccess32,
|
|
||||||
);
|
|
||||||
Ok(CpuPipelineAction::IncPC)
|
Ok(CpuPipelineAction::IncPC)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec_thumb_push_pop(
|
fn exec_thumb_push_pop(&mut self, bus: &mut Bus, insn: ThumbInstruction) -> CpuExecResult {
|
||||||
&mut self,
|
|
||||||
bus: &mut Bus,
|
|
||||||
insn: ThumbInstruction,
|
|
||||||
) -> CpuExecResult {
|
|
||||||
// (From GBATEK) Execution Time: nS+1N+1I (POP), (n+1)S+2N+1I (POP PC), or (n-1)S+2N (PUSH).
|
// (From GBATEK) Execution Time: nS+1N+1I (POP), (n+1)S+2N+1I (POP PC), or (n-1)S+2N (PUSH).
|
||||||
|
|
||||||
let is_pop = insn.is_load();
|
let is_pop = insn.is_load();
|
||||||
let mut pipeline_action = CpuPipelineAction::IncPC;
|
let mut pipeline_action = CpuPipelineAction::IncPC;
|
||||||
|
|
||||||
self.add_cycles(insn.pc, bus, NonSeq + MemoryAccess16);
|
|
||||||
|
|
||||||
let pc_lr_flag = insn.flag(ThumbInstruction::FLAG_R);
|
let pc_lr_flag = insn.flag(ThumbInstruction::FLAG_R);
|
||||||
let rlist = insn.register_list();
|
let rlist = insn.register_list();
|
||||||
if is_pop {
|
if is_pop {
|
||||||
|
@ -216,62 +157,30 @@ impl Core {
|
||||||
if pc_lr_flag {
|
if pc_lr_flag {
|
||||||
pop(self, bus, REG_PC);
|
pop(self, bus, REG_PC);
|
||||||
pipeline_action = CpuPipelineAction::Flush;
|
pipeline_action = CpuPipelineAction::Flush;
|
||||||
self.add_cycles(
|
|
||||||
self.pc,
|
|
||||||
bus,
|
|
||||||
Seq + MemoryAccess32
|
|
||||||
);
|
|
||||||
self.add_cycles(
|
|
||||||
self.pc + (self.word_size() as u32),
|
|
||||||
bus,
|
|
||||||
NonSeq + MemoryAccess16
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
self.add_cycle();
|
self.add_cycle();
|
||||||
} else {
|
} else {
|
||||||
self.add_cycles(
|
|
||||||
self.gpr[REG_SP],
|
|
||||||
bus,
|
|
||||||
NonSeq + MemoryAccess32
|
|
||||||
);
|
|
||||||
if pc_lr_flag {
|
if pc_lr_flag {
|
||||||
push(self, bus, REG_LR);
|
push(self, bus, REG_LR);
|
||||||
}
|
}
|
||||||
for r in rlist.into_iter().rev() {
|
for r in rlist.into_iter().rev() {
|
||||||
push(self, bus ,r);
|
push(self, bus, r);
|
||||||
}
|
}
|
||||||
self.add_cycles(self.gpr[REG_SP], bus, NonSeq + MemoryAccess32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(CpuPipelineAction::IncPC)
|
Ok(pipeline_action)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec_thumb_branch_with_cond(
|
fn exec_thumb_branch_with_cond(
|
||||||
&mut self,
|
&mut self,
|
||||||
bus: &mut Bus,
|
_bus: &mut Bus,
|
||||||
insn: ThumbInstruction,
|
insn: ThumbInstruction,
|
||||||
) -> CpuExecResult {
|
) -> CpuExecResult {
|
||||||
if !self.check_arm_cond(insn.cond()) {
|
if !self.check_arm_cond(insn.cond()) {
|
||||||
self.add_cycles(
|
|
||||||
insn.pc + (self.word_size() as u32),
|
|
||||||
bus,
|
|
||||||
Seq + MemoryAccess16,
|
|
||||||
);
|
|
||||||
Ok(CpuPipelineAction::IncPC)
|
Ok(CpuPipelineAction::IncPC)
|
||||||
} else {
|
} else {
|
||||||
// +1N
|
|
||||||
self.add_cycles(insn.pc, bus, NonSeq + MemoryAccess32);
|
|
||||||
let offset = insn.offset8() as i8 as i32;
|
let offset = insn.offset8() as i8 as i32;
|
||||||
self.pc = (insn.pc as i32).wrapping_add(offset) as u32;
|
self.pc = (insn.pc as i32).wrapping_add(offset) as u32;
|
||||||
|
|
||||||
// +2S
|
|
||||||
self.add_cycles(self.pc, bus, Seq + MemoryAccess32);
|
|
||||||
self.add_cycles(
|
|
||||||
self.pc + (self.word_size() as u32),
|
|
||||||
bus,
|
|
||||||
Seq + MemoryAccess32,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(CpuPipelineAction::Flush)
|
Ok(CpuPipelineAction::Flush)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -325,11 +325,8 @@ mod tests {
|
||||||
let insn = ThumbInstruction::decode(0x4801, 0x6).unwrap();
|
let insn = ThumbInstruction::decode(0x4801, 0x6).unwrap();
|
||||||
|
|
||||||
let bytes = vec![
|
let bytes = vec![
|
||||||
/* 0: */ 0x00, 0x00,
|
/* 0: */ 0x00, 0x00, /* 2: */ 0x00, 0x00, /* 4: */ 0x00, 0x00,
|
||||||
/* 2: */ 0x00, 0x00,
|
/* 6: <pc> */ 0x00, 0x00, /* 8: */ 0x00, 0x00, 0x00, 0x00,
|
||||||
/* 4: */ 0x00, 0x00,
|
|
||||||
/* 6: <pc> */ 0x00, 0x00,
|
|
||||||
/* 8: */ 0x00, 0x00, 0x00, 0x00,
|
|
||||||
/* c: */ 0x78, 0x56, 0x34, 0x12,
|
/* c: */ 0x78, 0x56, 0x34, 0x12,
|
||||||
];
|
];
|
||||||
let mut mem = BoxedMemory::new(bytes.into_boxed_slice());
|
let mut mem = BoxedMemory::new(bytes.into_boxed_slice());
|
||||||
|
|
Reference in a new issue