Skip to content

Commit

Permalink
sapemu: Start of uploader adapter and bugfixes in emulator
Browse files Browse the repository at this point in the history
  • Loading branch information
kleinesfilmroellchen committed Jan 5, 2024
1 parent afa2a39 commit c761eac
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 14 deletions.
1 change: 1 addition & 0 deletions sapemu/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! S-APU / SPC700 emulator.
#![deny(missing_docs, unused, clippy::all, clippy::pedantic, clippy::nursery, rustdoc::all)]
#![feature(slice_as_chunks)]

use std::path::PathBuf;
use std::time::Instant;
Expand Down
25 changes: 24 additions & 1 deletion sapemu/src/smp.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! S-SMP (SPC700 CPU) emulator.
mod ops;
pub mod upload;

use bitflags::bitflags;
use log::{debug, error, trace};
Expand Down Expand Up @@ -77,7 +78,7 @@ impl CpuIOPorts {
pub fn write(&mut self, port_number: u16, value: u8) {
Self::check_port_number(port_number);

trace!("CPUIO {0} = {1:02x} ({1})", port_number, value);
trace!("Write CPUIO {0} = {1:02x} ({1})", port_number, value);
self.write_ports[port_number as usize] = value;
}

Expand All @@ -98,6 +99,28 @@ impl CpuIOPorts {
trace!("Reset CPUIO {0}", port_number);
self.read_ports[port_number as usize] = 0;
}

/// Perform a read from the SMP.
#[inline]
pub fn read_from_smp<const PORT_NUMBER: u8>(&mut self) -> u8 {

Check failure on line 105 in sapemu/src/smp.rs

View workflow job for this annotation

GitHub Actions / clippy

docs for function which may panic missing `# Panics` section

error: docs for function which may panic missing `# Panics` section --> sapemu/src/smp.rs:105:2 | 105 | pub fn read_from_smp<const PORT_NUMBER: u8>(&mut self) -> u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: first possible panic found here --> sapemu/src/smp.rs:108:4 | 108 | panic!("Illegal port number {PORT_NUMBER}"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc = note: `#[deny(clippy::missing_panics_doc)]` implied by `#[deny(clippy::pedantic)]`
// FIXME: Should always be a compile-time check...
if PORT_NUMBER > 4 {
panic!("Illegal port number {PORT_NUMBER}");
}

Check failure on line 109 in sapemu/src/smp.rs

View workflow job for this annotation

GitHub Actions / clippy

only a `panic!` in `if`-then statement

error: only a `panic!` in `if`-then statement --> sapemu/src/smp.rs:107:3 | 107 | / if PORT_NUMBER > 4 { 108 | | panic!("Illegal port number {PORT_NUMBER}"); 109 | | } | |_________^ help: try instead: `assert!(!(PORT_NUMBER > 4), "Illegal port number {PORT_NUMBER}");` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert = note: `#[deny(clippy::manual_assert)]` implied by `#[deny(clippy::pedantic)]`
trace!("CPU read CPUIO {PORT_NUMBER} = {0:02x} ({0})", self.write_ports[PORT_NUMBER as usize]);
self.write_ports[PORT_NUMBER as usize]
}

/// Perform a read to the SMP.
#[inline]
pub fn write_to_smp<const PORT_NUMBER: u8>(&mut self, value: u8) {

Check failure on line 116 in sapemu/src/smp.rs

View workflow job for this annotation

GitHub Actions / clippy

docs for function which may panic missing `# Panics` section

error: docs for function which may panic missing `# Panics` section --> sapemu/src/smp.rs:116:2 | 116 | pub fn write_to_smp<const PORT_NUMBER: u8>(&mut self, value: u8) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: first possible panic found here --> sapemu/src/smp.rs:119:4 | 119 | panic!("Illegal port number {PORT_NUMBER}"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc
// FIXME: Should always be a compile-time check...
if PORT_NUMBER > 4 {
panic!("Illegal port number {PORT_NUMBER}");
}

Check failure on line 120 in sapemu/src/smp.rs

View workflow job for this annotation

GitHub Actions / clippy

only a `panic!` in `if`-then statement

error: only a `panic!` in `if`-then statement --> sapemu/src/smp.rs:118:3 | 118 | / if PORT_NUMBER > 4 { 119 | | panic!("Illegal port number {PORT_NUMBER}"); 120 | | } | |_________^ help: try instead: `assert!(!(PORT_NUMBER > 4), "Illegal port number {PORT_NUMBER}");` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert
trace!("CPU write CPUIO {PORT_NUMBER} = {0:02x} ({0})", value);
self.read_ports[PORT_NUMBER as usize] = value;
}
}

/// CPU clock rate (Hz)
Expand Down
22 changes: 9 additions & 13 deletions sapemu/src/smp/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -811,21 +811,21 @@ fn cmp_dp_imm(cpu: &mut Smp, memory: &mut Memory, cycle: usize, state: Instructi

match cycle {
0 => MicroArchAction::Continue(InstructionInternalState::default()),
2 => {
1 => {
let immediate = cpu.read_next_pc(memory);
MicroArchAction::Continue(InstructionInternalState::default().with_operand(immediate))
MicroArchAction::Continue(state.with_operand(immediate))
},
1 => {
2 => {
let address = cpu.read_next_pc(memory) as u16 + cpu.direct_page_offset();

Check failure on line 819 in sapemu/src/smp/ops.rs

View workflow job for this annotation

GitHub Actions / clippy

casting `u8` to `u16` may become silently lossy if you later change the type

error: casting `u8` to `u16` may become silently lossy if you later change the type --> sapemu/src/smp/ops.rs:819:18 | 819 | let address = cpu.read_next_pc(memory) as u16 + cpu.direct_page_offset(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::from(cpu.read_next_pc(memory))` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless note: the lint level is defined here --> sapemu/src/main.rs:2:44 | 2 | #![deny(missing_docs, unused, clippy::all, clippy::pedantic, clippy::nursery, rustdoc::all)] | ^^^^^^^^^^^^^^^^ = note: `#[deny(clippy::cast_lossless)]` implied by `#[deny(clippy::pedantic)]`
MicroArchAction::Continue(state.with_address(address))
},
3 => {
// Dummy read from destination address according to fullsnes.
let operand2 = cpu.read(state.address, memory);
MicroArchAction::Continue(state.with_operand2(operand2))
},
4 => {
let result = (state.operand2 as i8).wrapping_sub(state.operand as i8);

Check failure on line 827 in sapemu/src/smp/ops.rs

View workflow job for this annotation

GitHub Actions / clippy

casting `u8` to `i8` may wrap around the value

error: casting `u8` to `i8` may wrap around the value --> sapemu/src/smp/ops.rs:827:53 | 827 | let result = (state.operand2 as i8).wrapping_sub(state.operand as i8); | ^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap

Check failure on line 827 in sapemu/src/smp/ops.rs

View workflow job for this annotation

GitHub Actions / clippy

casting `u8` to `i8` may wrap around the value

error: casting `u8` to `i8` may wrap around the value --> sapemu/src/smp/ops.rs:827:17 | 827 | let result = (state.operand2 as i8).wrapping_sub(state.operand as i8); | ^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap = note: `#[deny(clippy::cast_possible_wrap)]` implied by `#[deny(clippy::pedantic)]`
trace!("cmp {:02x} - {:02x} = {:+}", state.operand2, state.operand, result);
cpu.set_negative(result as u8);

Check failure on line 829 in sapemu/src/smp/ops.rs

View workflow job for this annotation

GitHub Actions / clippy

casting `i8` to `u8` may lose the sign of the value

error: casting `i8` to `u8` may lose the sign of the value --> sapemu/src/smp/ops.rs:829:21 | 829 | cpu.set_negative(result as u8); | ^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss = note: `#[deny(clippy::cast_sign_loss)]` implied by `#[deny(clippy::pedantic)]`
cpu.set_zero(result as u8);

Check failure on line 830 in sapemu/src/smp/ops.rs

View workflow job for this annotation

GitHub Actions / clippy

casting `i8` to `u8` may lose the sign of the value

error: casting `i8` to `u8` may lose the sign of the value --> sapemu/src/smp/ops.rs:830:17 | 830 | cpu.set_zero(result as u8); | ^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss
cpu.set_subtract_carry(state.operand2 as i8, state.operand as i8);

Check failure on line 831 in sapemu/src/smp/ops.rs

View workflow job for this annotation

GitHub Actions / clippy

casting `u8` to `i8` may wrap around the value

error: casting `u8` to `i8` may wrap around the value --> sapemu/src/smp/ops.rs:831:49 | 831 | cpu.set_subtract_carry(state.operand2 as i8, state.operand as i8); | ^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap

Check failure on line 831 in sapemu/src/smp/ops.rs

View workflow job for this annotation

GitHub Actions / clippy

casting `u8` to `i8` may wrap around the value

error: casting `u8` to `i8` may wrap around the value --> sapemu/src/smp/ops.rs:831:27 | 831 | cpu.set_subtract_carry(state.operand2 as i8, state.operand as i8); | ^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap
Expand Down Expand Up @@ -921,17 +921,13 @@ fn mov_dp_imm(cpu: &mut Smp, memory: &mut Memory, cycle: usize, state: Instructi

match cycle {
0 => MicroArchAction::Continue(InstructionInternalState::default()),
2 => {
1 => {
let immediate = cpu.read_next_pc(memory);
MicroArchAction::Continue(InstructionInternalState { operand: immediate, ..Default::default() })
MicroArchAction::Continue(state.with_operand(immediate))
},
1 => {
2 => {
let address = cpu.read_next_pc(memory) as u16 + cpu.direct_page_offset();

Check failure on line 929 in sapemu/src/smp/ops.rs

View workflow job for this annotation

GitHub Actions / clippy

casting `u8` to `u16` may become silently lossy if you later change the type

error: casting `u8` to `u16` may become silently lossy if you later change the type --> sapemu/src/smp/ops.rs:929:18 | 929 | let address = cpu.read_next_pc(memory) as u16 + cpu.direct_page_offset(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::from(cpu.read_next_pc(memory))` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless
MicroArchAction::Continue(InstructionInternalState {
address,
operand: state.operand,
..Default::default()
})
MicroArchAction::Continue(state.with_address(address))
},
3 => {
// Dummy read from destination address according to fullsnes.
Expand Down Expand Up @@ -1231,7 +1227,7 @@ fn bne(cpu: &mut Smp, memory: &mut Memory, cycle: usize, state: InstructionInter
// branch if Z == 0
if !cpu.psw.contains(ProgramStatusWord::Zero) {
trace!("taking branch to {:+}", offset);
MicroArchAction::Continue(InstructionInternalState { relative: offset, ..Default::default() })
MicroArchAction::Continue(InstructionInternalState::default().with_relative(offset))
} else {
MicroArchAction::Next
}

Check failure on line 1233 in sapemu/src/smp/ops.rs

View workflow job for this annotation

GitHub Actions / clippy

unnecessary boolean `not` operation

error: unnecessary boolean `not` operation --> sapemu/src/smp/ops.rs:1228:4 | 1228 | / if !cpu.psw.contains(ProgramStatusWord::Zero) { 1229 | | trace!("taking branch to {:+}", offset); 1230 | | MicroArchAction::Continue(InstructionInternalState::default().with_relative(offset)) 1231 | | } else { 1232 | | MicroArchAction::Next 1233 | | } | |_____________^ | = help: remove the `!` and swap the blocks of the `if`/`else` = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#if_not_else = note: `#[deny(clippy::if_not_else)]` implied by `#[deny(clippy::pedantic)]`
Expand Down
115 changes: 115 additions & 0 deletions sapemu/src/smp/upload.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//! Data upload emulation
//!
//! This package implements the main CPU side of the SPC700 data upload routine as defined in the boot ROM.
use super::CpuIOPorts;

/// A single data block to be uploaded.
pub struct DataBlock {
/// Start address of the data block.
pub address: u16,
/// Data in the block.
data: [u8; 256],
/// Amount of data in the block.
#[allow(unused)]
length: u8,
}

impl DataBlock {
/// Create a data block from an iterable structure that yields bytes.
pub fn new(address: u16, iter: impl IntoIterator<Item = u8>) -> Option<Self> {
let elements: Vec<_> = iter.into_iter().collect();
if elements.len() > 256 {
None
} else {
let mut data = [0; 256];
let length = elements.len() as u8;

Check failure on line 26 in sapemu/src/smp/upload.rs

View workflow job for this annotation

GitHub Actions / clippy

casting `usize` to `u8` may truncate the value

error: casting `usize` to `u8` may truncate the value --> sapemu/src/smp/upload.rs:26:17 | 26 | let length = elements.len() as u8; | ^^^^^^^^^^^^^^^^^^^^ | = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation help: ... or use `try_from` and handle the error accordingly | 26 | let length = u8::try_from(elements.len()); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for (i, element) in elements.into_iter().enumerate() {
data[i] = element;
}
Some(Self { address, data, length })
}
}
}

/// Uploader responsible for implementing the upload routine.
pub struct Uploader {
current_address: u16,
remaining_blocks: Vec<DataBlock>,
entry_point: u16,
state: UploaderState,
}

/// Current state of the uploader.
#[derive(Clone, Copy, Debug, Default)]
#[allow(unused)]
enum UploaderState {
/// Uploader is waiting for signal value 0xAA in port 0.
#[default]
WaitingForAA,
/// Uploader is waiting for signal value 0xBB in port 1.
WaitingForBB,
/// Uploader is waiting for acknowledge of 0xCC in port 0.
WaitingForAcknowledge,
/// Uploader is done with uploading, all data has been sent.
Finished,
}

impl Uploader {
/// Create a new uploader without data.
pub fn new() -> Self {

Check failure on line 60 in sapemu/src/smp/upload.rs

View workflow job for this annotation

GitHub Actions / clippy

this method could have a `#[must_use]` attribute

error: this method could have a `#[must_use]` attribute --> sapemu/src/smp/upload.rs:60:2 | 60 | pub fn new() -> Self { | ^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn new() -> Self` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate = note: `#[deny(clippy::must_use_candidate)]` implied by `#[deny(clippy::pedantic)]`
Self {
current_address: 0,
remaining_blocks: Vec::new(),
entry_point: 0,
state: UploaderState::default(),
}
}

Check failure on line 67 in sapemu/src/smp/upload.rs

View workflow job for this annotation

GitHub Actions / clippy

you should consider adding a `Default` implementation for `Uploader`

error: you should consider adding a `Default` implementation for `Uploader` --> sapemu/src/smp/upload.rs:60:2 | 60 | / pub fn new() -> Self { 61 | | Self { 62 | | current_address: 0, 63 | | remaining_blocks: Vec::new(), ... | 66 | | } 67 | | } | |_____^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default note: the lint level is defined here --> sapemu/src/main.rs:2:31 | 2 | #![deny(missing_docs, unused, clippy::all, clippy::pedantic, clippy::nursery, rustdoc::all)] | ^^^^^^^^^^^ = note: `#[deny(clippy::new_without_default)]` implied by `#[deny(clippy::all)]` help: try adding this | 58 + impl Default for Uploader { 59 + fn default() -> Self { 60 + Self::new() 61 + } 62 + } |

/// Specify a code entry point the uploader will send after all blocks have been transferred. This API should be
/// used as soon as possible, in particular before all data blocks are sent.
pub fn with_entry_point(mut self, entry_point: u16) -> Self {

Check failure on line 71 in sapemu/src/smp/upload.rs

View workflow job for this annotation

GitHub Actions / clippy

this method could have a `#[must_use]` attribute

error: this method could have a `#[must_use]` attribute --> sapemu/src/smp/upload.rs:71:2 | 71 | pub fn with_entry_point(mut self, entry_point: u16) -> Self { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn with_entry_point(mut self, entry_point: u16) -> Self` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate
self.entry_point = entry_point;
self
}

Check failure on line 74 in sapemu/src/smp/upload.rs

View workflow job for this annotation

GitHub Actions / clippy

missing `#[must_use]` attribute on a method returning `Self`

error: missing `#[must_use]` attribute on a method returning `Self` --> sapemu/src/smp/upload.rs:71:2 | 71 | / pub fn with_entry_point(mut self, entry_point: u16) -> Self { 72 | | self.entry_point = entry_point; 73 | | self 74 | | } | |_____^ | = help: consider adding the `#[must_use]` attribute to the method or directly to the `Self` type = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#return_self_not_must_use = note: `#[deny(clippy::return_self_not_must_use)]` implied by `#[deny(clippy::pedantic)]`

Check failure on line 74 in sapemu/src/smp/upload.rs

View workflow job for this annotation

GitHub Actions / clippy

this could be a `const fn`

error: this could be a `const fn` --> sapemu/src/smp/upload.rs:71:2 | 71 | / pub fn with_entry_point(mut self, entry_point: u16) -> Self { 72 | | self.entry_point = entry_point; 73 | | self 74 | | } | |_____^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn

/// Add another data block to be transferred.
pub fn with_block(mut self, block: DataBlock) -> Self {

Check failure on line 77 in sapemu/src/smp/upload.rs

View workflow job for this annotation

GitHub Actions / clippy

this method could have a `#[must_use]` attribute

error: this method could have a `#[must_use]` attribute --> sapemu/src/smp/upload.rs:77:2 | 77 | pub fn with_block(mut self, block: DataBlock) -> Self { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn with_block(mut self, block: DataBlock) -> Self` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate
self.remaining_blocks.push(block);
self
}

Check failure on line 80 in sapemu/src/smp/upload.rs

View workflow job for this annotation

GitHub Actions / clippy

missing `#[must_use]` attribute on a method returning `Self`

error: missing `#[must_use]` attribute on a method returning `Self` --> sapemu/src/smp/upload.rs:77:2 | 77 | / pub fn with_block(mut self, block: DataBlock) -> Self { 78 | | self.remaining_blocks.push(block); 79 | | self 80 | | } | |_____^ | = help: consider adding the `#[must_use]` attribute to the method or directly to the `Self` type = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#return_self_not_must_use

/// Return the current byte the uploader needs to send.
pub fn current_byte(&self) -> Option<u8> {

Check failure on line 83 in sapemu/src/smp/upload.rs

View workflow job for this annotation

GitHub Actions / clippy

this method could have a `#[must_use]` attribute

error: this method could have a `#[must_use]` attribute --> sapemu/src/smp/upload.rs:83:2 | 83 | pub fn current_byte(&self) -> Option<u8> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn current_byte(&self) -> Option<u8>` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate
self.remaining_blocks
.first()
.map(|current_block| current_block.data[(self.current_address - current_block.address) as usize])
}

/// Runs the uploader as far as possible.
pub fn perform_step(&mut self, ports: &mut CpuIOPorts) {
match self.state {
UploaderState::WaitingForAA =>
if ports.read_from_smp::<0>() == 0xAA {
self.state = UploaderState::WaitingForBB;
},
UploaderState::WaitingForBB =>
if ports.read_from_smp::<1>() == 0xBB {
ports.write_to_smp::<1>(0x01);
self.write_address(ports);
ports.write_to_smp::<0>(0xCC);
self.state = UploaderState::WaitingForAcknowledge;
},
UploaderState::WaitingForAcknowledge => {
todo!()
},
UploaderState::Finished => {},
}
}

/// Write the current address to the
fn write_address(&self, ports: &mut CpuIOPorts) {
ports.write_to_smp::<2>((self.current_address & 0xFF) as u8);
ports.write_to_smp::<3>(((self.current_address >> 8) & 0xFF) as u8);
}
}

0 comments on commit c761eac

Please sign in to comment.