diff --git a/sw/host/tests/chip/pattgen/pattgen_ios.rs b/sw/host/tests/chip/pattgen/pattgen_ios.rs index e578d46a7f61d7..27342268671d8c 100644 --- a/sw/host/tests/chip/pattgen/pattgen_ios.rs +++ b/sw/host/tests/chip/pattgen/pattgen_ios.rs @@ -43,9 +43,9 @@ struct Opts { #[arg(long)] random_seed: Option, - /// Print a VCD dump the expected waves on failure. - #[arg(long, default_value = "false")] - print_expected_waves: bool, + /// Dump waves, even on success (waves are always printed on failured). + #[arg(long)] + dump_waves: bool, } const PATTGEN_PINS: &[(&str, &str)] = &[ @@ -78,6 +78,8 @@ enum TestCmd { #[derive(Clone)] struct ChannelSymbols { patt_polarity: u32, + patt_inactive_level_pda: u32, + patt_inactive_level_pcl: u32, patt_div: u32, patt_lower: u32, patt_upper: u32, @@ -94,6 +96,8 @@ struct Symbols { #[derive(Default, Debug)] struct PattGenChannelParams { patt_polarity: u8, + patt_inactive_level_pda: u8, + patt_inactive_level_pcl: u8, patt_div: u32, patt_lower: u32, patt_upper: u32, @@ -105,6 +109,8 @@ impl PattGenChannelParams { fn from_rng(rng: &mut T) -> Self { PattGenChannelParams { patt_polarity: rng.gen_range(0..=1), + patt_inactive_level_pda: rng.gen_range(0..=1), + patt_inactive_level_pcl: rng.gen_range(0..=1), patt_div: rng.gen_range(PATTGEN_MIN_DIV..=PATTGEN_MAX_DIV), patt_lower: rng.gen(), patt_upper: rng.gen(), @@ -114,31 +120,61 @@ impl PattGenChannelParams { } // Returns the list of clock edges where the data signal changes. - // Edge "1" corresponds to the first actual edge of the clock. Since - // the clock always starts at the right polarity, this means that the - // first data change can actually happen at edge "0" which does not - // exist but corresponds to the time of the first edge minus one period. - // Also return a bool indicate the last value of the pattern. - fn pattern_clock_edges(&self) -> (Vec, bool) { + // + // When the ENABLE bit is set, the pattgen will start outputting the first + // bit of the data. Depending on the inactive data value, this may or may + // not create an edge. At the same time, pattgen will also start outputting + // the clock signal. Depending on the inactive pcl level and the polarity, + // this may or may not create an edge. Let us call tFIRST_DATA_EDGE the first time + // where the data signal has an edge. Corresponding, call tFIRST_CLOCK_EDGE + // the first time where the clock signal has an edge. We number the clock edges + // by 0, 1, and so on so that the ith edge is at time tFIRST_CLOCK_EDGE + i*tHALF_PERIOD + // where tHALF_PERIOD is the half-period of the clock. Then in some combination + // of inactive pcl/data level, data pattern and clock polarity, it is possible for + // tFIRST_DATA_EDGE to occur at clock edge "-1", ie tFIRST_DATA_EDGE-tHALF_PERIOD. + // + // This function will return the clock edges at which a data edge is expected. They + // use the number scheme described above. In particular, it is possible for the first + // data edge to be at index "-1". The edges will include any potential edge needed to + // return to the inactive value. + fn pattern_clock_edges(&self) -> Vec { let mut edges = Vec::new(); let pattern = self.patt_lower as u64 | (self.patt_upper as u64) << 32; - let mut previous_data = false; // Data line starts low. - for rep in 0..(self.patt_rep as u32) { - for i in 0..(self.patt_len as u32) { + let mut previous_data = self.patt_inactive_level_pda != 0; // Data line starts at the inactive level. + + // Imagine a baseline scenario where the inactive clock level is 0 and polarity is 0. Then clock edge 0 + // is rising, edge 1 is falling and so on. In this scenario, the ith bit (i=0,1,...) of the pattern + // appears on the data line between clock edges 2i-1 and 2i+1. The loop below assumes this scenario + // and pushes a changes at edge 2i-1. To account for other scenarios, the following variable simply adds + // a constant shift to the 2i-1 above. + // If polarity is 1 and the inactive clock level is 1 then edge 0 of the clock is falling and edge 1 is + // rising so the ith pattern bit also appears between edges 2i-1 and 2i+1. Hence the polarity per say does + // not affect the edge numbering. + // However, if the inactive clock level does not match the polarity, for example if polarity is 0 but the + // inactive clock level is 1, then when ENABLE is set, the clock will have a first falling transition that + // does not correspond to a clock tick and the first "real" clock tick is edge 1. + // Hence the clock edge numbering is shifted by 1. The same happens if polarity is 1 and inactive pcl level is 0. + let edge_shift = if (self.patt_polarity != 0) == (self.patt_inactive_level_pcl != 0) { + -1 + } else { + 0 + }; + + for rep in 0..(self.patt_rep as i32) { + for i in 0..(self.patt_len as i32) { let cur_data = (pattern >> i) & 1 == 1; if cur_data != previous_data { - edges.push(2 * rep * (self.patt_len as u32) + 2 * i); + edges.push(2 * rep * (self.patt_len as i32) + 2 * i + edge_shift); previous_data = cur_data; } } } - // At the end, the hardware will stop with the value of the first bit which could - // create one more edge. - let steady_end_val = (self.patt_lower & 1) == 1; - if steady_end_val != previous_data { - edges.push(2 * self.patt_rep as u32 * self.patt_len as u32); + // After the pattern, the hardware will stop with the inactivel value which may create an edge. + if previous_data != (self.patt_inactive_level_pda != 0) { + edges.push(2 * (self.patt_rep as i32) * (self.patt_len as i32) + edge_shift) } - (edges, steady_end_val) + + edges } fn clock_period_ns(&self, clk_io_freq_hz: u64) -> u64 { @@ -171,23 +207,23 @@ struct SignalWave { } // Extract the edges from the waves. -fn extract_signal(gpio_mon: &GpioMon, sig_name: &str) -> Result { - let clk_idx = gpio_mon +fn extract_signal(waves: &Waves, sig_name: &str) -> Result { + let clk_idx = waves .signal_index(sig_name) .with_context(|| format!("no signal {sig_name} monitored?!"))?; - let initial_level = gpio_mon.initial_levels()[clk_idx]; + let initial_level = waves.initial_levels()[clk_idx]; let mut edges_ns = Vec::new(); let mut last_value = initial_level; - for evt in gpio_mon.all_events() { - if evt.signal_index != clk_idx as u8 { - continue; - } + for evt in waves + .all_events() + .filter(|evt| evt.signal_index == clk_idx as u8) + { ensure!( (evt.edge == Edge::Rising && !last_value) || (evt.edge == Edge::Falling && last_value), "signal recording is inconsistent, looks like some events were missed" ); last_value = !last_value; - edges_ns.push(gpio_mon.timestamp_to_ns(evt.timestamp)); + edges_ns.push(waves.timestamp_to_ns(evt.timestamp)); } Ok(SignalWave { initial_level, @@ -202,12 +238,12 @@ fn verify_clock( signal: &SignalWave, clk_io_freq_hz: u64, ) -> Result<()> { - // The signal starts with the polarity. + // The signal starts with the inactivel clock level. ensure!( - signal.initial_level == (params.patt_polarity == 1), - "clock starts with the wrong polarity" + signal.initial_level == (params.patt_inactive_level_pcl == 1), + "clock starts at the wrong level" ); - // The signal must tick exactly the required number of times. + // The signal must tick exactly the required number of times and go back to the inactive level. let expected_ticks = 2 * params.patt_len as usize * params.patt_rep as usize; ensure!( signal.edges_ns.len() == expected_ticks, @@ -239,42 +275,72 @@ fn verify_clock( Ok(()) } -// Verify that a data signal is valid: TODO +// Verify that a data signal is valid. +// This function will add a signal to the waves that contains +// the expected signal. fn verify_data( params: &PattGenChannelParams, clk: &SignalWave, - data: &SignalWave, + waves: &mut Waves, + data_sig_name: &str, clk_io_freq_hz: u64, ) -> Result<()> { - // Data signal must initially be low. - ensure!(!data.initial_level, "data starts at the wrong level"); - // Data signal must be low at the end as well. - ensure!(data.edges_ns.len() % 2 == 0, "data ends at the wrong level"); - - // Verify the edge timing. - let (pattern_edges, last_pattern_value) = params.pattern_clock_edges(); - // The first data edge occur on clock edge "0" which is one period before the first actual edge. - let virt_pre_edge = clk.edges_ns[0] - params.clock_period_ns(clk_io_freq_hz); - let mut clock_edges_ns = Vec::new(); - clock_edges_ns.push(virt_pre_edge); - clock_edges_ns.extend_from_slice(&clk.edges_ns); + // Then extract the data signal. + let data = extract_signal(waves, data_sig_name).context("data signal is invalid")?; + + // Compute data edge timing. + let pattern_edges = params.pattern_clock_edges(); + + // Function that returns the time of the nth edge. It needs to handle edge "-1" + // as well as an another virtual extra edge at the end of the clock signal. See pattern_clock_edges. + let clk_half_period = params.clock_period_ns(clk_io_freq_hz); + let clock_nth_edge = |clk_edge_idx: i32| { + if clk_edge_idx == -1 { + clk.edges_ns[0] - clk_half_period + } else if clk_edge_idx == clk.edges_ns.len() as i32 { + clk.edges_ns.last().unwrap() + clk_half_period + } else { + clk.edges_ns[clk_edge_idx as usize] + } + }; + + // Create a signal in the waves with the expected signal. + let mut previous_data_val = params.patt_inactive_level_pda != 0; + let expected_sig_idx = waves.add_signal(format!("{data_sig_name}_expected"), previous_data_val); + + for &clk_edge_idx in &pattern_edges { + let expected_clk_edge = clock_nth_edge(clk_edge_idx); + waves.add_event(MonitoringEvent { + signal_index: expected_sig_idx as u8, + edge: if previous_data_val { + Edge::Falling + } else { + Edge::Rising + }, + timestamp: waves.ns_to_timestamp(expected_clk_edge), + }); + previous_data_val = !previous_data_val; + } + waves.sort_events(); + + // Data signal must initially be at the inactive data. + ensure!( + data.initial_level == (params.patt_inactive_level_pda != 0), + "data starts at the wrong level" + ); for (i, &clk_edge_idx) in pattern_edges.iter().enumerate() { - assert!((clk_edge_idx as usize) < clock_edges_ns.len()); - let expected_clk_edge = clock_edges_ns[clk_edge_idx as usize]; - ensure!(i < data.edges_ns.len(), - "edge {i}: expected a data edge at time {expected_clk_edge}ns (clock edge {clk_edge_idx})"); + let expected_clk_edge = clock_nth_edge(clk_edge_idx); let actual_edge = data.edges_ns[i]; ensure!( expected_clk_edge.abs_diff(actual_edge) < MAX_CLOCK_JITTER_NS, - "edge {i}: expected a data edge at time {expected_clk_edge}ns (clock edge {clk_edge_idx}) but got time {actual_edge}ns" + "data edge {i}: expected a data edge at time {expected_clk_edge}ns (clock edge {clk_edge_idx}) but got time {actual_edge}ns" ); } - // Check for spurious edges at the end: if the last pattern value is one, there will be one extra edge to go down to zero. - let expected_pattern_edge_count = pattern_edges.len() + if last_pattern_value { 1 } else { 0 }; + // Check for spurious edges at the end. ensure!( - expected_pattern_edge_count == data.edges_ns.len(), - "data has unexpected edges after the pattern at time {}", + pattern_edges.len() == data.edges_ns.len(), + "data has unexpected edges after the pattern, e.g. at time {}", data.edges_ns.last().unwrap() ); @@ -284,110 +350,20 @@ fn verify_data( // Verify that the waves match the pattgen parameters. fn verify_waves( params: &PattGenChannelParams, - gpio_mon: &GpioMon, + waves: &mut Waves, clk_sig: &str, data_sig: &str, clk_io_freq_hz: u64, ) -> Result<()> { // First extract the clock signal. - let clk = extract_signal(gpio_mon, clk_sig).context("clock signal is invalid")?; + let clk = extract_signal(waves, clk_sig).context("clock signal is invalid")?; // Verify the clock signal. verify_clock(params, &clk, clk_io_freq_hz).context("clock signal is invalid")?; - // Then extract the data signal. - let data = extract_signal(gpio_mon, data_sig).context("data signal is invalid")?; // Verify the clock signals. - verify_data(params, &clk, &data, clk_io_freq_hz).context("data signal is invalid")?; + verify_data(params, &clk, waves, data_sig, clk_io_freq_hz).context("data signal is invalid")?; Ok(()) } -// Print a VCD dump of the expected channel waves. -fn dump_expected_waves(params: &PattGenParams, clk_io_freq_hz: u64) { - let mut pin_names = Vec::new(); - let mut initial_levels = Vec::new(); - for i in 0..CHANNEL_COUNT { - pin_names.push(format!("pda{i}_tx")); - // Data channels always start low. - initial_levels.push(false); - pin_names.push(format!("pcl{i}_tx")); - // Clock channels start at the right polarity if enabled. - initial_levels - .push((params.channel_enable >> i) & 1 == 1 && params.channels[i].patt_polarity == 1); - } - - let mut waves = Waves::new( - pin_names, - initial_levels, - 0, - // Timestamps will be in nanoseconds. - 1_000_000_000, - ); - - // Create events for channels one after the other and sort at the end. - let mut events = Vec::new(); - for (chan_idx, chan) in params.channels.iter().enumerate() { - // Add a small offset between initial time and first edge. - let initial_delay_ns = chan.clock_period_ns(clk_io_freq_hz); - // Only consider active channels. - if (params.channel_enable >> chan_idx) & 1 == 0 { - continue; - } - let data_sig_idx = 2 * chan_idx as u8; - let clk_sig_idx = data_sig_idx + 1; - let clock_ticks = chan.patt_len as u64 * chan.patt_rep as u64; - // Add clock. - for i in 0..clock_ticks { - events.push(MonitoringEvent { - signal_index: clk_sig_idx, - edge: if chan.patt_polarity == 0 { - Edge::Rising - } else { - Edge::Falling - }, - timestamp: initial_delay_ns + (2 * i + 1) * chan.clock_period_ns(clk_io_freq_hz), - }); - events.push(MonitoringEvent { - signal_index: clk_sig_idx, - edge: if chan.patt_polarity == 1 { - Edge::Rising - } else { - Edge::Falling - }, - timestamp: initial_delay_ns + (2 * i + 2) * chan.clock_period_ns(clk_io_freq_hz), - }); - } - // Add data. - let (pattern, steady_end_val) = chan.pattern_clock_edges(); - for (idx, clk_edge) in pattern.iter().enumerate() { - events.push(MonitoringEvent { - signal_index: data_sig_idx, - edge: if (idx % 2) == 0 { - Edge::Rising - } else { - Edge::Falling - }, - timestamp: initial_delay_ns - + *clk_edge as u64 * chan.clock_period_ns(clk_io_freq_hz), - }); - } - // Add falling edge after pattern with some margin. - if steady_end_val { - events.push(MonitoringEvent { - signal_index: data_sig_idx, - edge: Edge::Falling, - timestamp: initial_delay_ns - + (2 * clock_ticks + 1) * chan.clock_period_ns(clk_io_freq_hz), - }); - } - } - events.sort_by(|a, b| a.timestamp.cmp(&b.timestamp)); - waves.add_events(&events); - - log::info!( - "====[ VCD dump of expected waves ]====\n{}\n====[ end dump ]====", - waves.dump_vcd() - ); -} - fn pattgen_ios( opts: &Opts, transport: &TransportWrapper, @@ -425,25 +401,27 @@ fn pattgen_ios( let _ = UartConsole::wait_for(uart, r"TEST DONE", opts.timeout)?; // Stop monitoring. let _ = gpio_mon.read(false)?; + // Get the waves. + let mut waves: Waves = gpio_mon.try_into()?; // Compare the waves. for i in 0..CHANNEL_COUNT { if (params.channel_enable >> i) & 1 == 1 { let res = verify_waves( ¶ms.channels[i], - &gpio_mon, + &mut waves, &format!("pcl{i}_tx"), &format!("pda{i}_tx"), clk_io_freq_hz, ); - if res.is_err() && opts.print_expected_waves { - log::info!("dump waves"); - dump_expected_waves(¶ms, clk_io_freq_hz); + if res.is_err() || opts.dump_waves { + log::info!( + "====[ VCD dump ]====\n{}\n====[ end dump ]====", + waves.dump_vcd() + ); } res.with_context(|| format!("channel {i} did not meet expectations"))?; } } - // If we suceeded, do not dump waves. - gpio_mon.dump_on_drop(false); } // Tell device to stop. let _ = UartConsole::wait_for(uart, r"SiVal: waiting for commands", opts.timeout)?; @@ -467,6 +445,16 @@ fn write_params(uart: &dyn Uart, syms: &Symbols, params: &PattGenParams) -> Resu chan_syms.patt_polarity, &chan_param.patt_polarity.to_le_bytes(), )?; + MemWriteReq::execute( + uart, + chan_syms.patt_inactive_level_pcl, + &chan_param.patt_inactive_level_pcl.to_le_bytes(), + )?; + MemWriteReq::execute( + uart, + chan_syms.patt_inactive_level_pda, + &chan_param.patt_inactive_level_pda.to_le_bytes(), + )?; MemWriteReq::execute(uart, chan_syms.patt_div, &chan_param.patt_div.to_le_bytes())?; MemWriteReq::execute( uart, @@ -531,6 +519,18 @@ fn read_channel_symbols<'data: 'file, 'file, T: Object<'data, 'file>>( idx, size_of_val(&dummy_params.patt_polarity), )?, + patt_inactive_level_pcl: read_channel_symbol( + elf, + "kPattInactiveLevelPcl", + idx, + size_of_val(&dummy_params.patt_inactive_level_pcl), + )?, + patt_inactive_level_pda: read_channel_symbol( + elf, + "kPattInactiveLevelPda", + idx, + size_of_val(&dummy_params.patt_inactive_level_pda), + )?, patt_div: read_channel_symbol(elf, "kPattDiv", idx, size_of_val(&dummy_params.patt_div))?, patt_lower: read_channel_symbol( elf,