// 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::traits::SignalTransitionType; use libreda_sta::{ liberty_library, models::nldm_cell_delay::NLDMCellModel, traits::TimingQuery, RiseFall, SimpleSTA, StaError, }; use libreda_sta::{ClockDefinition, 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 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("TOP").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 [clk, in1, out1] = ["clk", "in1", "out1"].map(|name| sta.pin_by_name(&top, name).expect("pin not found")); let period = ns(10.); let clk_id = sta.create_clock( TerminalId::PinId(clk), ClockDefinition::new( period, NLDMSignal::new(ns(0.01), ns(0.0)).with_polarity(SignalTransitionType::Rise), NLDMSignal::new(ns(0.01), period / 2.).with_polarity(SignalTransitionType::Fall), ), ); 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)?; let timed = sta.update_timing()?; // 1. Get required arrival times. let required_signal = timed .report_rat(TerminalId::PinId(in1), RiseFall::Rise) .expect("required arrival time should be known"); assert_approx_eq!(required_signal.earliest_arrival_time.unwrap(), ns(0.0)); let clock_delay = ns(2.0); // clock goes through 2 inverters. assert_approx_eq!( required_signal.latest_arrival_time.unwrap(), period + clock_delay - ns(1.0) // t_setup - ns(3.0) // delay of NAND2X1 ); let slack_in1 = timed .report_slack(TerminalId::PinId(in1), RiseFall::Rise) .expect("slack should be defined"); assert!(slack_in1.hold_slack.unwrap().is_sign_positive()); assert!(slack_in1.setup_slack.unwrap().is_sign_positive()); 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 TOP(clk, in1, out1); input clk; input in1; output out1; wire clkbuf1_c; wire clkbuf2_c; wire y; wire nand1_out; wire dff1_out; INVX1 clkbuf1 ( .A(clk), .Y(clkbuf1_c) ); INVX1 clkbuf2 ( .A(clkbuf1_c), .Y(clkbuf2_c) ); NAND2X1 nand1 ( .A(in1), .B(dff1_out), .Y(nand1_out) ); DFFPOSX1 dff1 ( .CLK(clkbuf2_c), .D(nand1_out), .Q(dff1_out) ); assign out1 = dff1_out; endmodule module INVX1(A, Y); input A; output Y; endmodule module NAND2X1(A, B, Y); input A; input B; output Y; endmodule module DFFPOSX1(D, Q, CLK); input D; input CLK; output Q; 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.0" ); } 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 ( "3.0" ); } rise_transition(scalar) { values ( "0.1" ); } cell_fall(scalar) { values ( "3.0" ); } fall_transition(scalar) { values ( "0.1" ); } } timing() { related_pin : "B"; timing_sense : negative_unate; cell_rise(scalar) { values ( "3.0" ); } rise_transition(scalar) { values ( "0.1" ); } cell_fall(scalar) { values ( "3.0" ); } fall_transition(scalar) { values ( "0.1" ); } } } } cell (DFFPOSX1) { cell_leakage_power : 54.9774; ff (DS0000,P0000) { next_state : "D"; clocked_on : "CLK"; } pin(CLK) { direction : input; capacitance : 0.008; rise_capacitance : 0.0081; fall_capacitance : 0.0079; clock : true; min_pulse_width_high : 0.1; min_pulse_width_low : 0.05; } pin(D) { direction : input; capacitance : 0.00181554; rise_capacitance : 0.00181554; fall_capacitance : 0.00129019; timing() { related_pin : "CLK"; timing_type : hold_rising; rise_constraint(scalar) { values ( "1" ); } fall_constraint(scalar) { values ( "1" ); } } timing() { related_pin : "CLK"; timing_type : setup_rising; rise_constraint(scalar) { values ( "1" ); } fall_constraint(scalar) { values ( "1" ); } } } pin(Q) { direction : output; capacitance : 0; rise_capacitance : 0; fall_capacitance : 0; max_capacitance : 0.5; function : "DS0000"; timing() { related_pin : "CLK"; timing_sense : non_unate; timing_type : rising_edge; cell_rise(scalar) { values ( "1.1" ); } cell_fall(scalar) { values ( "1.0" ); } rise_transition(scalar) { values ( "0.1" ); } 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(); }