// SPDX-FileCopyrightText: 2024 Thomas Kramer // SPDX-License-Identifier: AGPL-3.0-or-later //! Test delay computation in combinational circuits. //! No clocks used. use libreda_db::prelude::*; use libreda_sta::models::nldm_cell_delay::{MinMax, NLDMRequiredSignal, NLDMSignal}; use libreda_sta::{ liberty_library, models::nldm_cell_delay::NLDMCellModel, traits::TimingQuery, RiseFall, SimpleSTA, StaError, }; use libreda_sta::{InputSignal, RequiredSignalArg}; use uom::si::f64::*; use uom::si::time::nanosecond; fn ns(t: f64) -> Time { Time::new::(t) } macro_rules! assert_approx_eq { ($a:expr, $b:expr) => { let (a, b) = ($a, $b); let tolerance = ns(1e-6); assert!((a - b).abs() < tolerance, "{:?} != {:?}", a, b) }; } #[test] fn test_delay_in_combinational_circuit() -> Result<(), StaError> { let library = get_liberty(); let netlist = create_test_netlist(); log::info!("Linking timing library."); let timing_lib = liberty_library::LibertyTimingLibrary::new( &library, |cell_name| netlist.cell_by_name(cell_name), // Link names to netlist IDs. |cell_id, pin_name| netlist.pin_by_name(cell_id, pin_name), // Link names to netlist IDs. ) .map_err(|e| { log::error!("Failed to prepare timing library: {}", e); StaError::Other(format!("Failed to prepare timing library: {}", e)) })?; let nldm_cell_lib = NLDMCellModel::new(&timing_lib, &&netlist); // Need `&&netlist` to match type. let top = netlist.cell_by_name("AND3").expect("top cell not found."); // Create STA engine which borrows the netlist and therefore has only read-access. let mut sta = SimpleSTA::new(&netlist, top.clone(), nldm_cell_lib).expect("failed to initialize STA"); // Get pin IDs. let [out1, in1, in2, in3] = ["out1", "in1", "in2", "in3"] .map(|name| sta.pin_by_name(&top, name).expect("pin not found")); let input_signal = InputSignal::new(NLDMSignal::new(ns(0.1), ns(0.0))); sta.set_input_signal(in1, input_signal)?; sta.set_input_signal(in2, input_signal)?; sta.set_input_signal(in3, input_signal)?; let timed = sta.update_timing()?; let output_delay_rise = timed .report_aat(TerminalId::PinId(out1), RiseFall::Rise) .expect("output delay should be known"); let output_delay_fall = timed .report_aat(TerminalId::PinId(out1), RiseFall::Fall) .expect("output delay should be known"); // Path through in3-nand2:B-nand1:Y-inv2:A-inv2:Y-out1 assert_approx_eq!(output_delay_rise.delay(MinMax::Min), ns(3.1)); // Path through in1-nand1:A-nand1:Y-inv1:A-inv1:Y-nand2:A-nand2:Y-inv2:A-inv2:Y-out1 assert_approx_eq!(output_delay_rise.delay(MinMax::Max), ns(6.2)); // Path through in3-nand2:B-nand1:Y-inv2:A-inv2:Y-out1 assert_approx_eq!(output_delay_fall.delay(MinMax::Min), ns(3.01)); // Path through in1-nand1:A-nand1:Y-inv1:A-inv1:Y-nand2:A-nand2:Y-inv2:A-inv2:Y-out1 assert_approx_eq!(output_delay_fall.delay(MinMax::Max), ns(6.02)); Ok(()) } /// Test propagation of required signals in a combinational circuit. /// /// 1. Compute the required signals at all inputs such that the required arrival times are met at the output. /// 2. Apply this required signals at the inputs and update the timing. /// 3. The constraint at the output should be met exactly. #[test] fn test_constraint_in_combinational_circuit() -> Result<(), StaError> { let library = get_liberty(); let netlist = create_test_netlist(); log::info!("Linking timing library."); let timing_lib = liberty_library::LibertyTimingLibrary::new( &library, |cell_name| netlist.cell_by_name(cell_name), // Link names to netlist IDs. |cell_id, pin_name| netlist.pin_by_name(cell_id, pin_name), // Link names to netlist IDs. ) .map_err(|e| { log::error!("Failed to prepare timing library: {}", e); StaError::Other(format!("Failed to prepare timing library: {}", e)) })?; let nldm_cell_lib = NLDMCellModel::new(&timing_lib, &&netlist); // Need `&&netlist` to match type. let top = netlist.cell_by_name("AND3").expect("top cell not found."); // Create STA engine which borrows the netlist and therefore has only read-access. let mut sta = SimpleSTA::new(&netlist, top.clone(), nldm_cell_lib).expect("failed to initialize STA"); // Get pin IDs. let [out1, in1, in2, in3] = ["out1", "in1", "in2", "in3"] .map(|name| sta.pin_by_name(&top, name).expect("pin not found")); let input_slew = ns(0.1); let input_signal = InputSignal::new(NLDMSignal::new(input_slew, ns(0.0))); sta.set_input_signal(in1, input_signal)?; sta.set_input_signal(in2, input_signal)?; sta.set_input_signal(in3, input_signal)?; let earliest_arrival_time = ns(10.); let latest_arrival_time = ns(20.); let required_output_signal = RequiredSignalArg::new(NLDMRequiredSignal::new( Some(earliest_arrival_time), Some(latest_arrival_time), )); sta.set_required_output_signal(out1, required_output_signal)?; let timed = sta.update_timing()?; // 1. Get required arrival times. let mut new_input_signals = vec![]; for pin in [in1, in2, in3] { for rise_fall in [RiseFall::Rise, RiseFall::Fall] { let required_signal = timed .report_rat(TerminalId::PinId(pin), rise_fall) .expect("required arrival time should be known"); new_input_signals.push((pin, rise_fall, required_signal)); } } // 2. Apply required signals to the inputs such that the output constraint is exactly met. for (pin, rise_fall, required_signal) in new_input_signals { let mut signal = NLDMSignal::new(input_slew, required_signal.earliest_arrival_time.unwrap()) .with_polarity(rise_fall.into()); signal.set_arrival_time(MinMax::Max, required_signal.latest_arrival_time.unwrap()); sta.set_input_signal(pin, InputSignal::new(signal))?; } // 3. Compute new timing. let timed = sta.update_timing()?; for rise_fall in [RiseFall::Rise, RiseFall::Fall] { let output_delay = timed .report_aat(TerminalId::PinId(out1), rise_fall) .expect("output delay should be known"); assert_approx_eq!(output_delay.delay(MinMax::Min), earliest_arrival_time); assert_approx_eq!(output_delay.delay(MinMax::Max), latest_arrival_time); } Ok(()) } /// Create a test netlist. pub fn create_test_netlist() -> Chip { use libreda_db::prelude::NetlistReader; use libreda_structural_verilog::StructuralVerilogReader; let mut chip = Chip::new(); let data = r#" module AND3(in1, in2, in3, out1); input in1; input in2; input in3; output out1; wire a; wire y; wire nand1; wire nand2; wire and1; wire and2; NAND2X1 nand_inst1 ( .A(in1), .B(in2), .Y(nand1) ); INVX1 inv_inst1 ( .A(nand1), .Y(and1) ); NAND2X1 nand_inst2 ( .A(and1), .B(in3), .Y(nand2) ); INVX1 inv_inst2 ( .A(nand2), .Y(and2) ); assign out1 = and2; endmodule module INVX1(A, Y); input A; output Y; endmodule module NAND2X1(A, B, Y); input A; input B; output Y; endmodule "#; let mut bytes = data.as_bytes(); let reader = StructuralVerilogReader::new(); reader .read_into_netlist(&mut bytes, &mut chip) .expect("Failed to read verilog netlist."); chip } /// Get simple liberty library. /// Cells use a constant delay model. fn get_liberty() -> liberty_io::Group { let liberty_data = r#" library(tiny_library) { delay_model : table_lookup; time_unit : "1ns"; voltage_unit : "1V"; current_unit : "1uA"; pulling_resistance_unit : "1kohm"; leakage_power_unit : "1nW"; capacitive_load_unit (1,pf); cell (INVX1) { pin(A) { direction : input; capacitance : 0.0015; rise_capacitance : 0.0016; fall_capacitance : 0.0014; } pin(Y) { direction : output; capacitance : 0; rise_capacitance : 0; fall_capacitance : 0; max_capacitance : 0.2; function : "(!A)"; timing() { related_pin : "A"; timing_sense : negative_unate; cell_rise(scalar) { values ( "1.1" ); } rise_transition(scalar) { values ( "0.1" ); } cell_fall(scalar) { values ( "1.0" ); } fall_transition(scalar) { values ( "0.1" ); } } } } cell (NAND2X1) { area : 1.8; pin(A) { direction : input; capacitance : 0.002; rise_capacitance : 0.0021; fall_capacitance : 0.0019; } pin(B) { direction : input; capacitance : 0.002; rise_capacitance : 0.0021; fall_capacitance : 0.0019; } pin(Y) { direction : output; capacitance : 0; rise_capacitance : 0; fall_capacitance : 0; max_capacitance : 0; function : "(!(A B))"; timing() { related_pin : "A"; timing_sense : negative_unate; cell_rise(scalar) { values ( "2.01" ); } rise_transition(scalar) { values ( "0.1" ); } cell_fall(scalar) { values ( "2.0" ); } fall_transition(scalar) { values ( "0.1" ); } } timing() { related_pin : "B"; timing_sense : negative_unate; cell_rise(scalar) { values ( "2.01" ); } rise_transition(scalar) { values ( "0.1" ); } cell_fall(scalar) { values ( "2.0" ); } fall_transition(scalar) { values ( "0.1" ); } } } } } "#; let mut slice = liberty_data.as_bytes(); liberty_io::read_liberty_bytes(&mut slice).expect("Failed to read or parse liberty library.") } #[test] fn test_read_simple_liberty() { // Test if we can parse the liberty data. let _ = get_liberty(); }