diff --git a/doc/src/tutorials/index.rst b/doc/src/tutorials/index.rst index 1c25145ef32..b9c661fb374 100644 --- a/doc/src/tutorials/index.rst +++ b/doc/src/tutorials/index.rst @@ -10,3 +10,4 @@ Tutorials arch/index titan_benchmarks/index timing_simulation/index + timing_analysis/index diff --git a/doc/src/tutorials/timing_analysis/index.rst b/doc/src/tutorials/timing_analysis/index.rst new file mode 100644 index 00000000000..6f93faa4c0e --- /dev/null +++ b/doc/src/tutorials/timing_analysis/index.rst @@ -0,0 +1,170 @@ +.. _timing_analysis_tutorial: + +Post-Implementation Timing Analysis +----------------------------------- + +This tutorial describes how to perform static timing analysis (STA) on a circuit which has +been implemented by :ref:`VPR` using OpenSTA, an external timing analysis tool. + +External timing analysis can be useful since VPR's timing analyzer (Tatum) does +not support all timing constraints and does not provide a TCL interface to allow +you to directly interrogate the timing graph. VPR also has limited support for +timing exceptions such as multi-cycles and false paths, which tools like OpenSTA +have better support for. + +Some external tools can also ingest more complex timing models (e.g. four +transition rr, rf, fr, ff delays vs. VTR's modeling of all transitions having +the same min,max range). + +.. _fig_timing_analysis_design_cycle: + +.. figure:: timing_analysis_design_cycle.png + + Post-implementation timing analysis design cycle. + +A user design cycle which would use post-implementation timing analysis could perform the following: + 1. Run VPR with the timing commands it can support (simplified constraints). + 2. Perform timing analysis on the resulting netlist using OpenSTA with + more complex timing commands. + 3. The user can then modify the design to meet the complex timing constraints based on the timing report produced by OpenSTA. + 4. The design can then be fed back into VPR and the process can repeat until all constraints are met. + +Generating the Post-Implementation Netlist for STA +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For this tutorial, we will be using the ``clma`` :ref:`benchmark ` +targetting the ``k6_frac_N10_frac_chain_mem32K_40nm.xml`` architecture. + +We will first create a working directory to hold all the timing analysis files: + +.. code-block:: console + + $ mkdir timing_analysis_tut + $ cd timing_analysis_tut + +Next we will copy over the benchmark and FPGA architecture into the working +directory for convenience: + +.. code-block:: console + + $ cp $VTR_ROOT/vtr_flow/benchmarks/blif/clma.blif . + $ cp $VTR_ROOT/vtr_flow/arch/timing/k6_frac_N10_frac_chain_mem32K_40nm.xml . + +.. note:: Replace :term:`$VTR_ROOT` with the root directory of the VTR source tree + +To perform timing analysis externally to VTR, we need to provide an SDC file +which will contain the timing constraints on the clocks and I/Os in the circuit. +For this tutorial, we will use the following ``clma.sdc`` file: + +.. code-block:: tcl + :linenos: + :caption: SDC file ``clma.sdc`` used for timing analysis. + + # Set pclk to be a clock with a 16ns period. + create_clock -period 16 pclk + + # Set the input delays of all input ports in the clma design to be 0 relative to pclk. + set_input_delay -clock pclk -max 0 [get_ports {pi*}] + + # Set the output delays of all output ports in the clma design to be 0 relative to pclk. + set_output_delay -clock pclk -max 0 [get_ports {p__*}] + +Next, we can generate the post-implementation netlist and other necessary files +for timing analysis using VPR. + +.. code-block:: console + + $ vpr \ + $ k6_frac_N10_frac_chain_mem32K_40nm.xml \ + $ clma.blif \ + $ --route_chan_width 100 \ + $ --sdc_file clma.sdc \ + $ --gen_post_synthesis_netlist on \ + $ --gen_post_implementation_sdc on \ + $ --post_synth_netlist_unconn_inputs gnd \ + $ --post_synth_netlist_module_parameters off + +In this command, we provide the architecture, circuit, the channel width, and +the SDC file. The other four commands are what generate the necessary netlist +files for timing analysis: + * ``--gen_post_synthesis_netlist on``: This will generate the post-implementation netlist as a Verilog file. + * ``--gen_post_implementation_sdc on``: This will have VPR generate a new SDC file which contains extra timing information (e.g. clock delays) based on how VPR implemented the design. + * ``--post_synth_netlist_unconn_inputs gnd``: For timing analysis with OpenSTA, we should be explicit about how we handle unconnected signal ports. Here we just ground them for simplicity. + * ``--post_synth_netlist_module_parameters off``: OpenSTA does not allow parameters to be used in the netlist. This command tells VPR to generate a netlist without using parameters. + +Once VPR has completed, we should see the generated Verilog netlist, SDF file, and SDC file: + +.. code-block:: console + + $ ls *.v *.sdf *.sdc + top_post_synthesis.sdc top_post_synthesis.sdf top_post_synthesis.v + + +Performing Timing Analysis using OpenSTA +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To perform static timing analysis for this tutorial, we will be using OpenSTA (https://github.com/parallaxsw/OpenSTA ). +Other STA tools can be used, however they may use slightly different commands. + +First, install OpenSTA onto your system. Building from source is a good option, +which can be done using the following instructions: +https://github.com/parallaxsw/OpenSTA?tab=readme-ov-file#build-from-source + +After OpenSTA is installed, we can perfrom static timing analysis on the post-implementation +netlist generated by VPR. + +It is easiest to write a ``sdf_delays.tcl`` file to setup and configure the timing analysis: + +.. code-block:: tcl + :linenos: + :caption: OpenSTA TCL file ``sdf_delays.tcl``. Note that :term:`$VTR_ROOT` should be replaced with the relevant path. + + # Read a skeleton of a liberty file which contains just enough information to + # allow OpenSTA to perform timing analysis on the post-synthesized netlist using + # an SDF file. This contains descriptions of the timing arcs of the primitives + # in the circuit. + read_liberty $VTR_ROOT/vtr_flow/primitives.lib + + # Read the post-implementation netlist generated by VPR. + read_verilog top_post_synthesis.v + + # Link the top-level design. + link_design top + + # Read the post-synthesis SDF file. + read_sdf top_post_synthesis.sdf + + # Read the SDC commands generated by VPR. + read_sdc top_post_synthesis.sdc + + # Report the setup and hold timing checks using OpenSTA and write them to files. + report_checks -group_path_count 100 -digits 3 -path_delay max > open_sta_report_timing.setup.rpt + report_checks -group_path_count 100 -digits 3 -path_delay min > open_sta_report_timing.hold.rpt + + # Report the minimum period of the clocks and their fmax. + report_clock_min_period + + # Exit OpenSTA's TCL terminal. + # This can be removed if you want terminal access to write TCL commands after + # executing the prior commands. + exit + +Now that we have a ``.tcl`` file, we can launch OpenSTA from the terminal and run it: + +.. code-block:: console + + $ sta sdf_delays.tcl + +Running this command will open a TCL terminal which will execute all of the commands +in ``sdf_delays.tcl``. The TCL file above will write setup and hold timing reports (similar to +the reports written by VPR), report the minimum period of all clocks, and then exit the OpenSTA TCL terminal. + +You can compare the timing reports generated by OpenSTA (``open_sta_report_timing.{setup/hold}.rpt``) +to the timing reports generated by VPR (``report_timing.{setup/hold}.rpt``). +You can also compare the minimum period reported by OpenSTA with the final +period reported by VTR at the bottom of ``vpr_stdout.log``. + +The TCL file above is just an example of what OpenSTA can do. For full documentation +of the different commands available in OpenSTA, see: +https://github.com/parallaxsw/OpenSTA/blob/master/doc/OpenSTA.pdf + diff --git a/doc/src/tutorials/timing_analysis/timing_analysis_design_cycle.png b/doc/src/tutorials/timing_analysis/timing_analysis_design_cycle.png new file mode 100644 index 00000000000..98b5e766297 Binary files /dev/null and b/doc/src/tutorials/timing_analysis/timing_analysis_design_cycle.png differ diff --git a/vtr_flow/primitives.lib b/vtr_flow/primitives.lib new file mode 100644 index 00000000000..0674aca964d --- /dev/null +++ b/vtr_flow/primitives.lib @@ -0,0 +1,370 @@ +/** + * @file + * @author Alex Singer + * @date May 2025 + * @brief A skeleton liberty library used to import the timing connectivity of a + * post-synthesis netlist (from VTR) into OpenSTA. + * + * This file contains just enough information to allow OpenSTA to use a provided + * SDF file for timing analysis of the netlist. + * + * This file only defines the primitives that VPR defines as "library models". + * This includes LUTs (.names) and Flip-Flops (.latch). For user models (the + * models defined in the "models" section of the architecture description file), + * one should create another liberty file. + */ + +library (VTRPrimitives) { + + /* General Attributes */ + delay_model : table_lookup; + + /* Units Attributes */ + time_unit : "1ns"; + + /* Threshold Definitions */ + /* These are the default values according to the Liberty User Manual */ + slew_lower_threshold_pct_fall : 20.00 ; + slew_lower_threshold_pct_rise : 20.00 ; + slew_upper_threshold_pct_fall : 80.00 ; + slew_upper_threshold_pct_rise : 80.00 ; + input_threshold_pct_fall : 50.00 ; + input_threshold_pct_rise : 50.00 ; + output_threshold_pct_fall : 50.00 ; + output_threshold_pct_rise : 50.00 ; + + /* Bus types used for the LUT cells to allow their inputs to be arrays.*/ + type (BUS4) { + base_type: array; + data_type: bit; + bit_width: 4; + bit_from: 3; + bit_to: 0; + } + type (BUS5) { + base_type: array; + data_type: bit; + bit_width: 5; + bit_from: 4; + bit_to: 0; + } + type (BUS6) { + base_type: array; + data_type: bit; + bit_width: 6; + bit_from: 5; + bit_to: 0; + } + type (BUS16) { + base_type: array; + data_type: bit; + bit_width: 16; + bit_from: 15; + bit_to: 0; + } + type (BUS32) { + base_type: array; + data_type: bit; + bit_width: 32; + bit_from: 31; + bit_to: 0; + } + type (BUS64) { + base_type: array; + data_type: bit; + bit_width: 64; + bit_from: 63; + bit_to: 0; + } + + /** + * @brief FPGA interconnect module. This cell acts as a wire in the post- + * implementation netlist to add delays on connections between + * primitives (due to routing delays). + * + * INPUTS: + * datain + * OUPUTS: + * dataout + */ + cell (fpga_interconnect) { + pin (datain) { + direction: input; + } + pin (dataout) { + direction: output; + function: "datain"; + + timing() { + related_pin: "datain"; + timing_sense: positive_unate; + + cell_fall(scalar) { + values("0.0"); + } + cell_rise(scalar) { + values("0.0"); + } + fall_transition(scalar) { + values("0.0"); + } + rise_transition(scalar) { + values("0.0"); + } + } + } + } + + /** + * @brief 4-input LUT module. + * + * INPUTS: + * in: + * The input pins of the LUT, as an array. + * mask: + * The LUT mask that defines the output of the LUT as a function + * of the input. mask[0] is the output if all the inputs are 0, and + * mask[2^k - 1] is the output if all the inputs are 1. + * OUPUTS: + * out + */ + cell (LUT_4) { + bus (mask) { + bus_type: "BUS16"; + direction: input; + } + bus (in) { + bus_type: "BUS4"; + direction: input; + } + pin (out) { + direction: output; + function: "(mask[0] & !in[0] & !in[1] & !in[2] & !in[3]) | (mask[1] & in[0] & !in[1] & !in[2] & !in[3]) | (mask[2] & !in[0] & in[1] & !in[2] & !in[3]) | (mask[3] & in[0] & in[1] & !in[2] & !in[3]) | (mask[4] & !in[0] & !in[1] & in[2] & !in[3]) | (mask[5] & in[0] & !in[1] & in[2] & !in[3]) | (mask[6] & !in[0] & in[1] & in[2] & !in[3]) | (mask[7] & in[0] & in[1] & in[2] & !in[3]) | (mask[8] & !in[0] & !in[1] & !in[2] & in[3]) | (mask[9] & in[0] & !in[1] & !in[2] & in[3]) | (mask[10] & !in[0] & in[1] & !in[2] & in[3]) | (mask[11] & in[0] & in[1] & !in[2] & in[3]) | (mask[12] & !in[0] & !in[1] & in[2] & in[3]) | (mask[13] & in[0] & !in[1] & in[2] & in[3]) | (mask[14] & !in[0] & in[1] & in[2] & in[3]) | (mask[15] & in[0] & in[1] & in[2] & in[3])"; + + timing() { + related_pin: "in"; + timing_sense: non_unate; + + cell_fall(scalar) { + values("0.0"); + } + cell_rise(scalar) { + values("0.0"); + } + fall_transition(scalar) { + values("0.0"); + } + rise_transition(scalar) { + values("0.0"); + } + } + } + } + + /** + * @brief 5-input LUT module. + * + * INPUTS: + * in: + * The input pins of the LUT, as an array. + * mask: + * The LUT mask that defines the output of the LUT as a function + * of the input. mask[0] is the output if all the inputs are 0, and + * mask[2^k - 1] is the output if all the inputs are 1. + * OUPUTS: + * out + */ + cell (LUT_5) { + bus (mask) { + bus_type: "BUS32"; + direction: input; + } + bus (in) { + bus_type: "BUS5"; + direction: input; + } + pin (out) { + direction: output; + function: "(mask[0] & !in[0] & !in[1] & !in[2] & !in[3] & !in[4]) | (mask[1] & in[0] & !in[1] & !in[2] & !in[3] & !in[4]) | (mask[2] & !in[0] & in[1] & !in[2] & !in[3] & !in[4]) | (mask[3] & in[0] & in[1] & !in[2] & !in[3] & !in[4]) | (mask[4] & !in[0] & !in[1] & in[2] & !in[3] & !in[4]) | (mask[5] & in[0] & !in[1] & in[2] & !in[3] & !in[4]) | (mask[6] & !in[0] & in[1] & in[2] & !in[3] & !in[4]) | (mask[7] & in[0] & in[1] & in[2] & !in[3] & !in[4]) | (mask[8] & !in[0] & !in[1] & !in[2] & in[3] & !in[4]) | (mask[9] & in[0] & !in[1] & !in[2] & in[3] & !in[4]) | (mask[10] & !in[0] & in[1] & !in[2] & in[3] & !in[4]) | (mask[11] & in[0] & in[1] & !in[2] & in[3] & !in[4]) | (mask[12] & !in[0] & !in[1] & in[2] & in[3] & !in[4]) | (mask[13] & in[0] & !in[1] & in[2] & in[3] & !in[4]) | (mask[14] & !in[0] & in[1] & in[2] & in[3] & !in[4]) | (mask[15] & in[0] & in[1] & in[2] & in[3] & !in[4]) | (mask[16] & !in[0] & !in[1] & !in[2] & !in[3] & in[4]) | (mask[17] & in[0] & !in[1] & !in[2] & !in[3] & in[4]) | (mask[18] & !in[0] & in[1] & !in[2] & !in[3] & in[4]) | (mask[19] & in[0] & in[1] & !in[2] & !in[3] & in[4]) | (mask[20] & !in[0] & !in[1] & in[2] & !in[3] & in[4]) | (mask[21] & in[0] & !in[1] & in[2] & !in[3] & in[4]) | (mask[22] & !in[0] & in[1] & in[2] & !in[3] & in[4]) | (mask[23] & in[0] & in[1] & in[2] & !in[3] & in[4]) | (mask[24] & !in[0] & !in[1] & !in[2] & in[3] & in[4]) | (mask[25] & in[0] & !in[1] & !in[2] & in[3] & in[4]) | (mask[26] & !in[0] & in[1] & !in[2] & in[3] & in[4]) | (mask[27] & in[0] & in[1] & !in[2] & in[3] & in[4]) | (mask[28] & !in[0] & !in[1] & in[2] & in[3] & in[4]) | (mask[29] & in[0] & !in[1] & in[2] & in[3] & in[4]) | (mask[30] & !in[0] & in[1] & in[2] & in[3] & in[4]) | (mask[31] & in[0] & in[1] & in[2] & in[3] & in[4])"; + + timing() { + related_pin: "in"; + timing_sense: non_unate; + + cell_fall(scalar) { + values("0.0"); + } + cell_rise(scalar) { + values("0.0"); + } + fall_transition(scalar) { + values("0.0"); + } + rise_transition(scalar) { + values("0.0"); + } + } + } + } + + /** + * @brief 6-input LUT module. + * + * INPUTS: + * in: + * The input pins of the LUT, as an array. + * mask: + * The LUT mask that defines the output of the LUT as a function + * of the input. mask[0] is the output if all the inputs are 0, and + * mask[2^k - 1] is the output if all the inputs are 1. + * OUPUTS: + * out + */ + cell (LUT_6) { + bus (mask) { + bus_type: "BUS64"; + direction: input; + } + bus (in) { + bus_type: "BUS6"; + direction: input; + } + pin (out) { + direction: output; + function: "(mask[0] & !in[0] & !in[1] & !in[2] & !in[3] & !in[4] & !in[5]) | (mask[1] & in[0] & !in[1] & !in[2] & !in[3] & !in[4] & !in[5]) | (mask[2] & !in[0] & in[1] & !in[2] & !in[3] & !in[4] & !in[5]) | (mask[3] & in[0] & in[1] & !in[2] & !in[3] & !in[4] & !in[5]) | (mask[4] & !in[0] & !in[1] & in[2] & !in[3] & !in[4] & !in[5]) | (mask[5] & in[0] & !in[1] & in[2] & !in[3] & !in[4] & !in[5]) | (mask[6] & !in[0] & in[1] & in[2] & !in[3] & !in[4] & !in[5]) | (mask[7] & in[0] & in[1] & in[2] & !in[3] & !in[4] & !in[5]) | (mask[8] & !in[0] & !in[1] & !in[2] & in[3] & !in[4] & !in[5]) | (mask[9] & in[0] & !in[1] & !in[2] & in[3] & !in[4] & !in[5]) | (mask[10] & !in[0] & in[1] & !in[2] & in[3] & !in[4] & !in[5]) | (mask[11] & in[0] & in[1] & !in[2] & in[3] & !in[4] & !in[5]) | (mask[12] & !in[0] & !in[1] & in[2] & in[3] & !in[4] & !in[5]) | (mask[13] & in[0] & !in[1] & in[2] & in[3] & !in[4] & !in[5]) | (mask[14] & !in[0] & in[1] & in[2] & in[3] & !in[4] & !in[5]) | (mask[15] & in[0] & in[1] & in[2] & in[3] & !in[4] & !in[5]) | (mask[16] & !in[0] & !in[1] & !in[2] & !in[3] & in[4] & !in[5]) | (mask[17] & in[0] & !in[1] & !in[2] & !in[3] & in[4] & !in[5]) | (mask[18] & !in[0] & in[1] & !in[2] & !in[3] & in[4] & !in[5]) | (mask[19] & in[0] & in[1] & !in[2] & !in[3] & in[4] & !in[5]) | (mask[20] & !in[0] & !in[1] & in[2] & !in[3] & in[4] & !in[5]) | (mask[21] & in[0] & !in[1] & in[2] & !in[3] & in[4] & !in[5]) | (mask[22] & !in[0] & in[1] & in[2] & !in[3] & in[4] & !in[5]) | (mask[23] & in[0] & in[1] & in[2] & !in[3] & in[4] & !in[5]) | (mask[24] & !in[0] & !in[1] & !in[2] & in[3] & in[4] & !in[5]) | (mask[25] & in[0] & !in[1] & !in[2] & in[3] & in[4] & !in[5]) | (mask[26] & !in[0] & in[1] & !in[2] & in[3] & in[4] & !in[5]) | (mask[27] & in[0] & in[1] & !in[2] & in[3] & in[4] & !in[5]) | (mask[28] & !in[0] & !in[1] & in[2] & in[3] & in[4] & !in[5]) | (mask[29] & in[0] & !in[1] & in[2] & in[3] & in[4] & !in[5]) | (mask[30] & !in[0] & in[1] & in[2] & in[3] & in[4] & !in[5]) | (mask[31] & in[0] & in[1] & in[2] & in[3] & in[4] & !in[5]) | (mask[32] & !in[0] & !in[1] & !in[2] & !in[3] & !in[4] & in[5]) | (mask[33] & in[0] & !in[1] & !in[2] & !in[3] & !in[4] & in[5]) | (mask[34] & !in[0] & in[1] & !in[2] & !in[3] & !in[4] & in[5]) | (mask[35] & in[0] & in[1] & !in[2] & !in[3] & !in[4] & in[5]) | (mask[36] & !in[0] & !in[1] & in[2] & !in[3] & !in[4] & in[5]) | (mask[37] & in[0] & !in[1] & in[2] & !in[3] & !in[4] & in[5]) | (mask[38] & !in[0] & in[1] & in[2] & !in[3] & !in[4] & in[5]) | (mask[39] & in[0] & in[1] & in[2] & !in[3] & !in[4] & in[5]) | (mask[40] & !in[0] & !in[1] & !in[2] & in[3] & !in[4] & in[5]) | (mask[41] & in[0] & !in[1] & !in[2] & in[3] & !in[4] & in[5]) | (mask[42] & !in[0] & in[1] & !in[2] & in[3] & !in[4] & in[5]) | (mask[43] & in[0] & in[1] & !in[2] & in[3] & !in[4] & in[5]) | (mask[44] & !in[0] & !in[1] & in[2] & in[3] & !in[4] & in[5]) | (mask[45] & in[0] & !in[1] & in[2] & in[3] & !in[4] & in[5]) | (mask[46] & !in[0] & in[1] & in[2] & in[3] & !in[4] & in[5]) | (mask[47] & in[0] & in[1] & in[2] & in[3] & !in[4] & in[5]) | (mask[48] & !in[0] & !in[1] & !in[2] & !in[3] & in[4] & in[5]) | (mask[49] & in[0] & !in[1] & !in[2] & !in[3] & in[4] & in[5]) | (mask[50] & !in[0] & in[1] & !in[2] & !in[3] & in[4] & in[5]) | (mask[51] & in[0] & in[1] & !in[2] & !in[3] & in[4] & in[5]) | (mask[52] & !in[0] & !in[1] & in[2] & !in[3] & in[4] & in[5]) | (mask[53] & in[0] & !in[1] & in[2] & !in[3] & in[4] & in[5]) | (mask[54] & !in[0] & in[1] & in[2] & !in[3] & in[4] & in[5]) | (mask[55] & in[0] & in[1] & in[2] & !in[3] & in[4] & in[5]) | (mask[56] & !in[0] & !in[1] & !in[2] & in[3] & in[4] & in[5]) | (mask[57] & in[0] & !in[1] & !in[2] & in[3] & in[4] & in[5]) | (mask[58] & !in[0] & in[1] & !in[2] & in[3] & in[4] & in[5]) | (mask[59] & in[0] & in[1] & !in[2] & in[3] & in[4] & in[5]) | (mask[60] & !in[0] & !in[1] & in[2] & in[3] & in[4] & in[5]) | (mask[61] & in[0] & !in[1] & in[2] & in[3] & in[4] & in[5]) | (mask[62] & !in[0] & in[1] & in[2] & in[3] & in[4] & in[5]) | (mask[63] & in[0] & in[1] & in[2] & in[3] & in[4] & in[5])"; + + timing() { + related_pin: "in"; + timing_sense: non_unate; + + cell_fall(scalar) { + values("0.0"); + } + cell_rise(scalar) { + values("0.0"); + } + fall_transition(scalar) { + values("0.0"); + } + rise_transition(scalar) { + values("0.0"); + } + } + } + } + + /** + * @brief D-Flip-Flop module. + * + * INPUTS: + * D: + * The input of the DFF, which will get latched on the rising clock + * edge. + * clock: + * The clock signal for the DFF. + * OUPUTS: + * Q: + * The current value stored in the latch. + * QN: + * The inverse of the current value stored in the latch. + */ + cell (DFF) { + ff (IQ, IQN) { + next_state: "D"; + clocked_on: "clock"; + } + + pin (D) { + direction: input; + + timing() { + related_pin: "clock"; + timing_type: hold_rising; + + fall_constraint(scalar) { + values("0.0"); + } + rise_constraint(scalar) { + values("0.0"); + } + } + + timing() { + related_pin: "clock"; + timing_type: setup_rising; + + fall_constraint(scalar) { + values("0.0"); + } + rise_constraint(scalar) { + values("0.0"); + } + } + } + + pin (clock) { + direction: input; + clock: true; + + timing() { + related_pin: "clock"; + timing_type: min_pulse_width; + + fall_constraint(scalar) { + values("0.0"); + } + rise_constraint(scalar) { + values("0.0"); + } + } + } + + pin (Q) { + direction: output; + function: "IQ"; + + timing() { + related_pin: "clock"; + timing_type: rising_edge; + timing_sense: non_unate; + + cell_fall(scalar) { + values("0.0"); + } + cell_rise(scalar) { + values("0.0"); + } + fall_transition(scalar) { + values("0.0"); + } + rise_transition(scalar) { + values("0.0"); + } + } + } + + pin (QN) { + direction: output; + function: "IQN"; + + timing() { + related_pin: "clock"; + timing_type: rising_edge; + timing_sense: non_unate; + + cell_fall(scalar) { + values("0.0"); + } + cell_rise(scalar) { + values("0.0"); + } + fall_transition(scalar) { + values("0.0"); + } + rise_transition(scalar) { + values("0.0"); + } + } + } + } +}