use color_eyre::eyre::Result;
use comfy_table::{Cell, CellAlignment};
use owo_colors::OwoColorize;
use probe_rs::Session;
use crate::classify::{BlockResult, Class, classify_all};
use crate::format::human_bytes;
use crate::heatmap::{self, CellColor};
use crate::io::{read_words, reset_and_halt, write_words};
use crate::render::{class_cell, class_inline, header_cell, make_table, step};
use crate::stability::print_stability;
pub struct SurveyResult {
pub readback: Vec<u32>,
pub blocks: Vec<BlockResult>,
}
pub fn full_sram_survey(
s: &mut Session,
start: u32,
end: u32,
block: u32,
cycles: u32,
invert_pattern: bool,
) -> Result<SurveyResult> {
assert!(end > start, "end must be > start");
assert!(
(end - start).is_multiple_of(block),
"(end - start) must be a multiple of block"
);
assert!(block.is_multiple_of(4), "block must be a multiple of 4");
assert!(cycles >= 1, "cycles must be >= 1");
let total_bytes = (end - start) as usize;
let n_words = total_bytes / 4;
let words_per_block = (block / 4) as usize;
let n_blocks = total_bytes / block as usize;
let pattern_label = if invert_pattern { "!addr" } else { "addr" };
if cycles == 1 {
step(&format!(
"sweeping {} blocks of {} ({} total) with pattern '{}'",
n_blocks,
human_bytes(block as u64),
human_bytes(total_bytes as u64),
pattern_label,
));
} else {
step(&format!(
"sweeping {} blocks of {} ({} total), {} reset cycles (write '{}' once, reset+read {} times)",
n_blocks,
human_bytes(block as u64),
human_bytes(total_bytes as u64),
cycles,
pattern_label,
cycles,
));
}
let mut pattern = vec![0u32; n_words];
for (i, w) in pattern.iter_mut().enumerate() {
let addr = start + (i as u32) * 4;
*w = if invert_pattern { !addr } else { addr };
}
step(&format!(
"writing {} of '{}'-as-data",
human_bytes((n_words * 4) as u64),
pattern_label,
));
write_words(s, start, &pattern)?;
let mut per_cycle_classes: Vec<Vec<Class>> = Vec::with_capacity(cycles as usize);
let mut last_blocks: Vec<BlockResult> = Vec::new();
let mut last_readback: Vec<u32> = Vec::new();
for cycle in 1..=cycles {
if cycles > 1 && !crate::render::is_quiet() {
println!();
println!(
"{} {}",
format!("Reset cycle {cycle}/{cycles}").bold().cyan(),
"(write was only done before cycle 1)".dimmed()
);
}
step("reset_and_halt (post-ROM, pre-user-code)");
reset_and_halt(s)?;
let readback = read_words(s, start, n_words)?;
let blocks = classify_all(start, block, n_blocks, words_per_block, &readback, &pattern);
render_heatmap(start, block, &blocks);
if cycles > 1 && !crate::render::is_quiet() {
let mut counts = [0usize; 4];
for b in &blocks {
counts[b.class as usize] += 1;
}
println!(
" {} {} | {} {} | {} {} | {} {}",
class_inline(Class::Safe),
counts[Class::Safe as usize],
class_inline(Class::Zero),
counts[Class::Zero as usize],
class_inline(Class::Ones),
counts[Class::Ones as usize],
class_inline(Class::Changed),
counts[Class::Changed as usize],
);
}
per_cycle_classes.push(blocks.iter().map(|b| b.class).collect());
last_blocks = blocks;
last_readback = readback;
}
if cycles == 1 {
print_runs_and_totals(&last_blocks, end, block);
} else {
print_stability(start, end, block, &per_cycle_classes);
}
Ok(SurveyResult {
readback: last_readback,
blocks: last_blocks,
})
}
pub fn render_heatmap(start: u32, block: u32, blocks: &[BlockResult]) {
let n_cells = blocks.len();
let subtitle = format!("(each cell = {})", human_bytes(block as u64));
heatmap::render("Heatmap", &subtitle, start, block, n_cells, |cell| {
crate::render::class_color(blocks[cell].class)
});
println!();
heatmap::legend(&[
(CellColor::Green, "SAFE (unmodified)"),
(CellColor::Blue, "ZERO (scrubbed to 0x00)"),
(CellColor::Magenta, "ONES (filled with 0xFF / undriven)"),
(CellColor::Red, "CHANGED (ROM working data)"),
]);
}
fn print_runs_and_totals(blocks: &[BlockResult], end: u32, block: u32) {
type Run = (u32, u32, Class, Option<(u32, u32, u32)>);
if crate::render::is_quiet() {
return;
}
let mut runs: Vec<Run> = Vec::new();
let mut run_start = blocks[0].addr;
let mut run_class = blocks[0].class;
let mut run_sample = blocks[0].first_diff;
for b in &blocks[1..] {
if b.class != run_class {
runs.push((run_start, b.addr, run_class, run_sample));
run_start = b.addr;
run_class = b.class;
run_sample = b.first_diff;
}
}
runs.push((run_start, end, run_class, run_sample));
println!();
println!("{}", "Runs".bold());
let mut t = make_table();
t.set_header(vec![
header_cell("Start"),
header_cell("End"),
header_cell("Size"),
header_cell("Class"),
header_cell("Sample mismatch"),
]);
for (rs, re, class, sample) in &runs {
let size = human_bytes((re - rs) as u64);
let sample_str = match (class, sample) {
(Class::Changed, Some((addr, expected, got))) => {
format!("@0x{addr:08X}: exp={expected:08x} got={got:08x}")
}
(Class::Safe, _) => String::from("(all words match address)"),
(Class::Zero, _) => String::from("(all 0x00000000)"),
(Class::Ones, _) => String::from("(all 0xFFFFFFFF)"),
_ => String::new(),
};
t.add_row(vec![
Cell::new(format!("0x{rs:08X}")),
Cell::new(format!("0x{re:08X}")),
Cell::new(size).set_alignment(CellAlignment::Right),
class_cell(*class),
Cell::new(sample_str).fg(comfy_table::Color::DarkGrey),
]);
}
println!("{t}");
let mut counts = [0usize; 4];
for b in blocks {
counts[b.class as usize] += 1;
}
println!();
println!("{}", "Totals".bold());
let mut t = make_table();
t.set_header(vec![
header_cell("Class"),
header_cell("Blocks"),
header_cell("Size"),
]);
for (i, &count) in counts.iter().enumerate() {
if count == 0 {
continue;
}
let class = match i {
0 => Class::Safe,
1 => Class::Zero,
2 => Class::Ones,
_ => Class::Changed,
};
let size = human_bytes(count as u64 * block as u64);
t.add_row(vec![
class_cell(class),
Cell::new(count.to_string()).set_alignment(CellAlignment::Right),
Cell::new(size).set_alignment(CellAlignment::Right),
]);
}
println!("{t}");
}