pub const PSRAM_SIZE: usize = 8 << 20;
const CMD_RESET_ENABLE: u8 = 0x66;
const CMD_RESET: u8 = 0x99;
const CMD_WRITE: u8 = 0x02;
const CMD_FAST_READ: u8 = 0x0B;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Phase {
Idle,
Cmd,
WriteAddr,
WriteData,
ReadAddr,
ReadDummy,
ReadData,
SilentNop,
}
pub struct Psram {
pub buffer: Box<[u8; PSRAM_SIZE]>,
pin_miso: u8,
pin_cs: u8,
pin_sck: u8,
pin_mosi: u8,
phase: Phase,
shift_in: u8,
shift_in_bits: u8,
shift_out: u8,
shift_out_bits: u8,
addr_bytes_seen: u8,
addr: u32,
reset_armed: bool,
prev_sck: bool,
prev_cs: bool,
latched_mosi: bool,
miso_bit: bool,
driving_miso: bool,
pub bytes_written: u64,
pub bytes_read: u64,
pub tick_count: u64,
pub cs_falling_count: u64,
}
impl Psram {
pub fn new(pin_miso: u8, pin_cs: u8, pin_sck: u8, pin_mosi: u8) -> Self {
let vec = vec![0u8; PSRAM_SIZE].into_boxed_slice();
let buffer: Box<[u8; PSRAM_SIZE]> = vec
.try_into()
.expect("vec of exactly PSRAM_SIZE bytes fits a sized Box");
Self {
buffer,
pin_miso,
pin_cs,
pin_sck,
pin_mosi,
phase: Phase::Idle,
shift_in: 0,
shift_in_bits: 0,
shift_out: 0,
shift_out_bits: 0,
addr_bytes_seen: 0,
addr: 0,
reset_armed: false,
prev_sck: false,
prev_cs: true,
latched_mosi: false,
miso_bit: false,
driving_miso: false,
bytes_written: 0,
bytes_read: 0,
tick_count: 0,
cs_falling_count: 0,
}
}
pub fn picogus() -> Self {
Self::new(0, 1, 2, 3)
}
pub fn pin_miso(&self) -> u8 {
self.pin_miso
}
pub fn pin_cs(&self) -> u8 {
self.pin_cs
}
pub fn pin_sck(&self) -> u8 {
self.pin_sck
}
pub fn pin_mosi(&self) -> u8 {
self.pin_mosi
}
pub fn reset_state(&mut self) {
self.phase = Phase::Idle;
self.shift_in = 0;
self.shift_in_bits = 0;
self.shift_out = 0;
self.shift_out_bits = 0;
self.addr_bytes_seen = 0;
self.addr = 0;
self.reset_armed = false;
self.latched_mosi = false;
self.miso_bit = false;
self.driving_miso = false;
}
pub fn tick(&mut self, pins: u32) -> Option<bool> {
self.tick_count = self.tick_count.wrapping_add(1);
let cs = ((pins >> self.pin_cs) & 1) != 0;
let sck = ((pins >> self.pin_sck) & 1) != 0;
let mosi = ((pins >> self.pin_mosi) & 1) != 0;
let cs_fell = !cs && self.prev_cs;
let cs_rose = cs && !self.prev_cs;
if cs_rose {
self.end_frame();
}
if cs_fell {
self.cs_falling_count = self.cs_falling_count.wrapping_add(1);
self.begin_frame();
}
if !cs {
let rising = sck && !self.prev_sck;
let falling = !sck && self.prev_sck;
if rising {
self.latched_mosi = mosi;
self.on_sck_rising();
} else if falling {
self.on_sck_falling();
}
}
self.prev_cs = cs;
self.prev_sck = sck;
if self.driving_miso {
Some(self.miso_bit)
} else {
None
}
}
fn begin_frame(&mut self) {
self.phase = Phase::Cmd;
self.shift_in = 0;
self.shift_in_bits = 0;
self.shift_out = 0;
self.shift_out_bits = 0;
self.addr_bytes_seen = 0;
self.addr = 0;
self.driving_miso = false;
self.miso_bit = false;
}
fn end_frame(&mut self) {
self.phase = Phase::Idle;
self.shift_in = 0;
self.shift_in_bits = 0;
self.shift_out = 0;
self.shift_out_bits = 0;
self.driving_miso = false;
self.miso_bit = false;
}
fn on_sck_rising(&mut self) {
self.shift_in = (self.shift_in << 1) | (self.latched_mosi as u8);
self.shift_in_bits += 1;
if self.shift_in_bits == 8 {
let byte = self.shift_in;
self.shift_in = 0;
self.shift_in_bits = 0;
self.consume_byte(byte);
}
}
fn on_sck_falling(&mut self) {
if self.shift_out_bits > 0 {
self.miso_bit = (self.shift_out & 0x80) != 0;
self.shift_out <<= 1;
self.shift_out_bits -= 1;
if self.shift_out_bits == 0 {
self.advance_read_byte();
}
}
}
fn consume_byte(&mut self, byte: u8) {
match self.phase {
Phase::Cmd => self.handle_command(byte),
Phase::WriteAddr => self.handle_addr_byte(byte, false),
Phase::WriteData => {
let off = (self.addr as usize) & (PSRAM_SIZE - 1);
self.buffer[off] = byte;
self.addr = self.addr.wrapping_add(1);
self.bytes_written += 1;
}
Phase::ReadAddr => self.handle_addr_byte(byte, true),
Phase::ReadDummy => {
self.phase = Phase::ReadData;
self.driving_miso = true;
self.advance_read_byte();
}
Phase::ReadData => {
}
Phase::Idle | Phase::SilentNop => {
}
}
}
fn handle_command(&mut self, byte: u8) {
match byte {
CMD_RESET_ENABLE => {
self.reset_armed = true;
self.phase = Phase::SilentNop;
}
CMD_RESET => {
if self.reset_armed {
self.reset_state();
} else {
self.phase = Phase::SilentNop;
}
}
CMD_WRITE => {
self.reset_armed = false;
self.phase = Phase::WriteAddr;
self.addr_bytes_seen = 0;
self.addr = 0;
}
CMD_FAST_READ => {
self.reset_armed = false;
self.phase = Phase::ReadAddr;
self.addr_bytes_seen = 0;
self.addr = 0;
}
_ => {
self.reset_armed = false;
self.phase = Phase::SilentNop;
}
}
}
fn handle_addr_byte(&mut self, byte: u8, is_read: bool) {
self.addr = (self.addr << 8) | (byte as u32);
self.addr_bytes_seen += 1;
if self.addr_bytes_seen == 3 {
self.addr &= (PSRAM_SIZE as u32) - 1;
if is_read {
self.phase = Phase::ReadDummy;
} else {
self.phase = Phase::WriteData;
}
}
}
fn advance_read_byte(&mut self) {
let off = (self.addr as usize) & (PSRAM_SIZE - 1);
self.shift_out = self.buffer[off];
self.shift_out_bits = 8;
self.addr = self.addr.wrapping_add(1);
self.bytes_read += 1;
}
pub fn phase_is_idle(&self) -> bool {
matches!(self.phase, Phase::Idle)
}
#[cfg(test)]
pub fn reset_armed(&self) -> bool {
self.reset_armed
}
#[cfg(test)]
pub fn bytes_written(&self) -> u64 {
self.bytes_written
}
}
#[cfg(test)]
mod tests {
use super::*;
const PIN_CS: u8 = 1;
const PIN_SCK: u8 = 2;
const PIN_MOSI: u8 = 3;
fn clock_byte(psram: &mut Psram, pins: &mut u32, byte: u8) -> u8 {
let mut out: u8 = 0;
for i in 0..8 {
let bit = (byte >> (7 - i)) & 1;
*pins = (*pins & !(1 << PIN_MOSI)) | ((bit as u32) << PIN_MOSI);
*pins &= !(1 << PIN_SCK);
let _ = psram.tick(*pins);
*pins |= 1 << PIN_SCK;
let miso = psram.tick(*pins).unwrap_or(false);
out = (out << 1) | (miso as u8);
}
*pins &= !(1 << PIN_SCK);
let _ = psram.tick(*pins);
out
}
fn cs_fall(psram: &mut Psram, pins: &mut u32) {
*pins &= !(1 << PIN_CS);
*pins &= !(1 << PIN_SCK);
let _ = psram.tick(*pins);
}
fn cs_rise(psram: &mut Psram, pins: &mut u32) {
*pins |= 1 << PIN_CS;
*pins &= !(1 << PIN_SCK);
let _ = psram.tick(*pins);
}
fn fresh() -> (Psram, u32) {
let psram = Psram::picogus();
let pins = 1u32 << PIN_CS;
(psram, pins)
}
#[test]
fn reset_enable_then_reset_clears_state() {
let (mut psram, mut pins) = fresh();
cs_fall(&mut psram, &mut pins);
clock_byte(&mut psram, &mut pins, 0x02); clock_byte(&mut psram, &mut pins, 0x00); cs_rise(&mut psram, &mut pins);
assert!(!psram.reset_armed());
cs_fall(&mut psram, &mut pins);
clock_byte(&mut psram, &mut pins, 0x66);
cs_rise(&mut psram, &mut pins);
assert!(psram.reset_armed(), "0x66 must arm reset");
cs_fall(&mut psram, &mut pins);
clock_byte(&mut psram, &mut pins, 0x99);
cs_rise(&mut psram, &mut pins);
assert!(
!psram.reset_armed(),
"0x99 after 0x66 must clear reset_armed"
);
assert!(psram.phase_is_idle());
}
#[test]
fn reset_alone_without_enable_is_nop() {
let (mut psram, mut pins) = fresh();
cs_fall(&mut psram, &mut pins);
clock_byte(&mut psram, &mut pins, 0x99); cs_rise(&mut psram, &mut pins);
assert!(!psram.reset_armed());
assert!(psram.phase_is_idle());
}
#[test]
fn write_round_trip() {
let (mut psram, mut pins) = fresh();
cs_fall(&mut psram, &mut pins);
clock_byte(&mut psram, &mut pins, 0x02); clock_byte(&mut psram, &mut pins, 0x00); clock_byte(&mut psram, &mut pins, 0x00); clock_byte(&mut psram, &mut pins, 0x10); clock_byte(&mut psram, &mut pins, 0xDE);
clock_byte(&mut psram, &mut pins, 0xAD);
clock_byte(&mut psram, &mut pins, 0xBE);
clock_byte(&mut psram, &mut pins, 0xEF);
cs_rise(&mut psram, &mut pins);
assert_eq!(&psram.buffer[0x10..0x14], &[0xDE, 0xAD, 0xBE, 0xEF]);
assert_eq!(psram.bytes_written(), 4);
}
#[test]
fn fast_read_returns_written_bytes() {
let (mut psram, mut pins) = fresh();
psram.buffer[0x10] = 0xDE;
psram.buffer[0x11] = 0xAD;
psram.buffer[0x12] = 0xBE;
psram.buffer[0x13] = 0xEF;
cs_fall(&mut psram, &mut pins);
clock_byte(&mut psram, &mut pins, 0x0B); clock_byte(&mut psram, &mut pins, 0x00); clock_byte(&mut psram, &mut pins, 0x00); clock_byte(&mut psram, &mut pins, 0x10); clock_byte(&mut psram, &mut pins, 0x00); let b0 = clock_byte(&mut psram, &mut pins, 0x00);
let b1 = clock_byte(&mut psram, &mut pins, 0x00);
let b2 = clock_byte(&mut psram, &mut pins, 0x00);
let b3 = clock_byte(&mut psram, &mut pins, 0x00);
cs_rise(&mut psram, &mut pins);
assert_eq!([b0, b1, b2, b3], [0xDE, 0xAD, 0xBE, 0xEF]);
}
#[test]
fn fast_read_dummy_cycles_are_ignored() {
let (mut psram, mut pins) = fresh();
psram.buffer[0x00] = 0x5A;
psram.buffer[0x01] = 0xA5;
cs_fall(&mut psram, &mut pins);
clock_byte(&mut psram, &mut pins, 0x0B);
clock_byte(&mut psram, &mut pins, 0x00);
clock_byte(&mut psram, &mut pins, 0x00);
clock_byte(&mut psram, &mut pins, 0x00);
clock_byte(&mut psram, &mut pins, 0xFF);
let b0 = clock_byte(&mut psram, &mut pins, 0x12);
let b1 = clock_byte(&mut psram, &mut pins, 0x34);
cs_rise(&mut psram, &mut pins);
assert_eq!([b0, b1], [0x5A, 0xA5]);
}
#[test]
fn cs_rise_mid_command_discards_state() {
let (mut psram, mut pins) = fresh();
cs_fall(&mut psram, &mut pins);
clock_byte(&mut psram, &mut pins, 0x02);
clock_byte(&mut psram, &mut pins, 0x00);
clock_byte(&mut psram, &mut pins, 0x00);
cs_rise(&mut psram, &mut pins);
assert!(psram.phase_is_idle());
cs_fall(&mut psram, &mut pins);
clock_byte(&mut psram, &mut pins, 0x02);
clock_byte(&mut psram, &mut pins, 0x00);
clock_byte(&mut psram, &mut pins, 0x00);
clock_byte(&mut psram, &mut pins, 0x20);
clock_byte(&mut psram, &mut pins, 0x77);
cs_rise(&mut psram, &mut pins);
assert_eq!(psram.buffer[0x20], 0x77);
assert_eq!(psram.buffer[0x00], 0);
assert_eq!(psram.buffer[0x10], 0);
}
#[test]
fn unknown_command_is_silent_nop() {
let (mut psram, mut pins) = fresh();
cs_fall(&mut psram, &mut pins);
clock_byte(&mut psram, &mut pins, 0x9F);
clock_byte(&mut psram, &mut pins, 0x00);
clock_byte(&mut psram, &mut pins, 0x00);
cs_rise(&mut psram, &mut pins);
assert!(psram.buffer[..].iter().all(|&b| b == 0));
cs_fall(&mut psram, &mut pins);
clock_byte(&mut psram, &mut pins, 0x02);
clock_byte(&mut psram, &mut pins, 0x00);
clock_byte(&mut psram, &mut pins, 0x00);
clock_byte(&mut psram, &mut pins, 0x00);
clock_byte(&mut psram, &mut pins, 0xAB);
cs_rise(&mut psram, &mut pins);
assert_eq!(psram.buffer[0x00], 0xAB);
}
#[test]
fn address_wraps_at_8mb() {
let (mut psram, mut pins) = fresh();
cs_fall(&mut psram, &mut pins);
clock_byte(&mut psram, &mut pins, 0x02);
clock_byte(&mut psram, &mut pins, 0x80); clock_byte(&mut psram, &mut pins, 0x00);
clock_byte(&mut psram, &mut pins, 0x01);
clock_byte(&mut psram, &mut pins, 0xC3);
cs_rise(&mut psram, &mut pins);
assert_eq!(psram.buffer[0x01], 0xC3);
}
#[test]
fn write_then_read_spanning_multiple_bytes() {
let (mut psram, mut pins) = fresh();
let base_addr: u32 = 0x12_3450;
let data: [u8; 16] = [
0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0,
0xF0, 0x00,
];
cs_fall(&mut psram, &mut pins);
clock_byte(&mut psram, &mut pins, 0x02);
clock_byte(&mut psram, &mut pins, (base_addr >> 16) as u8);
clock_byte(&mut psram, &mut pins, (base_addr >> 8) as u8);
clock_byte(&mut psram, &mut pins, base_addr as u8);
for b in &data {
clock_byte(&mut psram, &mut pins, *b);
}
cs_rise(&mut psram, &mut pins);
cs_fall(&mut psram, &mut pins);
clock_byte(&mut psram, &mut pins, 0x0B);
clock_byte(&mut psram, &mut pins, (base_addr >> 16) as u8);
clock_byte(&mut psram, &mut pins, (base_addr >> 8) as u8);
clock_byte(&mut psram, &mut pins, base_addr as u8);
clock_byte(&mut psram, &mut pins, 0x00); let mut got = [0u8; 16];
for (i, slot) in got.iter_mut().enumerate() {
*slot = clock_byte(&mut psram, &mut pins, i as u8);
}
cs_rise(&mut psram, &mut pins);
assert_eq!(&got, &data);
}
#[test]
fn tick_idle_without_cs_activity_stays_idle() {
let (mut psram, mut pins) = fresh();
for _ in 0..16 {
pins ^= 1 << PIN_SCK;
pins ^= 1 << PIN_MOSI;
let drive = psram.tick(pins);
assert!(drive.is_none());
}
assert!(psram.phase_is_idle());
}
}