• Skip to main content
  • Skip to search
  • Skip to footer
Cadence Home
  • This search text may be transcribed, used, stored, or accessed by our third-party service providers per our Cookie Policy and Privacy Policy.

  1. Blogs
  2. Verification
  3. Struggling to Rewrite Functionality in PSS? Import Functions…
Siddh Virani
Siddh Virani

Community Member

Blog Activity
Options
  • Subscribe by email
  • More
  • Cancel
Perspec
System Design and Verification
perspec system verifier
import function
pss

Struggling to Rewrite Functionality in PSS? Import Functions Streamlines

23 Apr 2026 • 9 minute read

One of the most powerful features of the Portable Stimulus Standard (PSS) is the ability to import functions from foreign languages like C or SystemVerilog. This capability is far more than a convenience; it solves a critical real-world challenge. A very common real-life use case is that many teams already have a substantial firmware/software API library (usually in C, sometimes SV), and they do not want to re-model that realization layer purely in native PSS terms (e.g., PSS registers or native PSS functions). Instead, they want PSS to orchestrate and sequence existing firmware APIs as part of scenario realization. Imported functions are what make that layering possible.

PSS models the what (intent, ordering, constraints, coverage), while foreign functions implement the how (actual FW calls and platform-specific behavior). This approach enables seamless integration of PSS scenarios with proven, production-quality code, ensuring code reuse, layered abstraction, and faster adoption (actual FW calls and platform-specific behavior).

Imagine you're developing a feature and realize the same functionality already exists in C or SystemVerilog. Instead of rewriting it in PSS, you can simply reuse that code—saving time and reducing the risk of bugs. Or consider tackling a complex logic problem and thinking, "If I could use XYZ language here, this would be easier." That's where PSS's foreign language support shines. It is introduced in the PSS 2.0 Language Reference Manual. Import functions allow integration with languages like C and SystemVerilog, making it especially useful when connecting a UVM/SV testbench with a PSS environment. You can specify the target language and embed foreign code directly within the function body. This capability works seamlessly across both target and solve platforms, making it a powerful tool for engineers looking to streamline workflows and leverage existing assets.

Please note that only static functions can be imported via the foreign language interface.

The sections below will help you with the following:

  • How to use import functions on the Solving platform and the target platform
  • How to use Perspec for the import function

Import Function with Target Platform

In the PSS model, the Import function on the target platform can be declared as mentioned below.

Function Declaration

import target function return_type function_name( input|output|inout  parameter );

Function Definition

In the exec declaration block, the definition of the function can be provided as mentioned below:

exec declaration <name_of_target_language> = “””
    return_type function_name ( parameters ){
         functionality implementation in the C
    }
“””

Another option for C implementation is to create a separate external library and link that library with the Perspec-generated executable. Example 1.1 shows this approach.

Example 1.1: Import Function with Target Platform with a Separate External Library

PSS file:

//--------------

// dma_c

extend component dma_c {

    import target static function void dma_program(int channel_id,sml_addr_t src_addr,sml_addr_t dst_addr,int size);

    import target static function void dma_start(int channel_id);

    import target static function void dma_wait_for_completion(int channel_id);

    import target static function bool dma_is_done(int channel_id);

   

    extend action transfer {       

        exec body {

            // calling the user firmware routine

            comp.dma_program(channel.instance_id,in_buff.mem_seg.addr,out_buff.mem_seg.addr,in_buff.mem_seg.size);

            comp.dma_start(channel.instance_id);

            while (!comp.dma_is_done(channel.instance_id)) {

                yield;

            }

        }

        exec post_solve {

            out_buff.data.bytes = in_buff.data.bytes;

        }

    }

}

C_File: dma_functions.h

#ifndef YAMM_STUB_DD_

#define YAMM_STUB_DD_

 

void dma_program(int chan_num,

                 void* src_buff,

                 void* dst_buff,

                 int size);

void dma_start(int chan_num);

void dma_wait_for_done(int chan_num);

int dma_is_done(int chan_num);

 

C File: dma_functions.c

#include "dma_functions.h"

#include <stdio.h>

#include <stdint.h>

#include <unistd.h>

void dma_program(int chan_num,

                 void* src_buff,

                 void* dst_buff,

                 int size) {

      printf("dma_program()\n");

      printf("  chan_num = %d\n",chan_num);

      printf("  src_buff = %p\n",src_buff);

      printf("  dst_buff = %p\n",dst_buff);

      printf("  size     = %d\n",size);

}

 

static long long int chan_end_time[2] = {-1, -1};

 

void dma_start(int chan_num) {

      printf("dma_start()\n");

      printf("  chan_num = %d\n",chan_num);

      yamm_rand_delay();

      chan_end_time[chan_num] = SLN_GET_TIME() + SLN_STABLE_RANDOM(500,700,0);

}

 

void dma_wait_for_done(int chan_num) {

  while (!dma_is_done(chan_num)) ; /* busy wait */

}

 

int dma_is_done(int chan_num) {

    int result = (SLN_GET_TIME() >= chan_end_time[chan_num]);

    if (result) {

        printf("dma_is_done()\n");

        printf("  chan_num = %d\n",chan_num);

    }

    return result;

}

PSS model in Example 1.1 is calling the functions of the DMA c library, using the import target function, and its definitions are present in separate C files.

Another example where a c implementation is present in the exec body. If you need the descramble_64bit_data functionality on the target platform and already have its implementation in C, there's no need to redevelop it in PSS. You can directly reuse existing functionality available in C, as demonstrated in Example 1.2.

Example 1.2: Import Function on Target Platform

PSS file:

import std_pkg::*;

component pss_top{

    import target static function bit[64] descramble_64bit_data(bit[64] scrambled_data);

    action test{

        rand bit[64] scrambled_data;

        bit[64] descrambled_data;

 

        exec body{

            descrambled_data = descramble_64bit_data(scrambled_data);

            message(LOW, "scrambled_data: %x, | descrambled_data: %x", scrambled_data, descrambled_data);

        };

    };

    exec declaration C = """

        unsigned short int lfsr_state = 0xABCD;

        unsigned char generate_prbs_bit(void) {

            // The new bit is the XOR of the tap positions (16, 5, 4, 3)

            // Note: Since lfsr_state is uint16_t, we use its bits directly.

            // The tap positions are relative to the *output* bit in some designs.

            // A more standard approach uses a shift register and taps the bits of the register.

            // Standard Galois LFSR implementation for a 16-bit polynomial

            unsigned char new_bit = ((lfsr_state >> 15) ^ (lfsr_state >> 4) ^ (lfsr_state >> 3) ^ (lfsr_state >> 2)) & 1;

            lfsr_state = (lfsr_state << 1) | new_bit;

            return (lfsr_state >> 15) & 1; // The output bit is typically the MSB after shift in this form

        }

        // Function to descramble 64 bits of data

        unsigned long long descramble_64bit_data(unsigned long long scrambled_data) {

            unsigned long long descrambled_data = 0;

            for (int i = 0; i < 64; ++i) {

                unsigned char prbs_bit = generate_prbs_bit();

                // Extract the i-th bit from the scrambled data

                unsigned char data_bit = (scrambled_data >> i) & 1;

                // Descramble the bit (XOR with PRBS bit)

                unsigned char descrambled_bit = data_bit ^ prbs_bit;

                // Place the descrambled bit into the result

                if (descrambled_bit) {

                    descrambled_data |= ((unsigned long long)1 << i);

                }

            }

            return descrambled_data;

        }

    """;

};

Composer View of Example 1.2: The Highlighted Part of the Snapshot Shows a Functional Call in the Generated C Code in the Perspec Composer Window

The descramble_64bit_data function is declared within the PSS model. It accepts 64-bit scrambled data as input and returns the corresponding 64-bit descrambled data. Since this function is implemented specifically for a target platform, it can only be invoked inside target execution blocks. For this reason, it is called within the exec body.

The exec declaration block contains the C implementation of descramble_64bit_data. Additionally, this function internally calls other C functions, which are also defined within the same exec declaration block.

Import Function on Solve Platform

In the PSS model, the import function on the solve platform can be declared as mentioned below.

Function Declaration

import solve function return_type function_name( input|output|inout parameter );

Function definitions should be placed in a separate C file based on the target language. Use a dedicated .c file containing the function definition. This file must be precompiled and linked at solve time.  For example, a C file can be compiled with gcc using the command below.

Command to Generate .so File

gcc -shared <path_of_c_file>.c -o <path_of_so_file>.so -fPIC;

Now the PSS file can be executed with the generated .so file using the command below.

Command to Run the PSS Model with the Generated .so File Using Perspec

perspec generate -pss_import_c_lib <path_of_so_file>.so -pss <path_of_pss_file>.pss – top_action test

Command to Run the PSS Model with the Generated .so File Using the Perspec Composer GUI

perspec compose -pss_import_c_lib <path_of_so_file>.so -pss <path_of_pss_file>.pss – top_action test

Here -pss_import_c_lib loads the specified shared library file(s). These files are supposed to contain the precompiled C implementations for imported solve C functions, to be invoked as part of the solve exec blocks execution.

Let’s take an example where the PSS model requires the logic for xor_scrambling, and you already have an implementation available in C, there’s no need to rewrite it from scratch. You can simply reuse the existing C code within the PSS model, as demonstrated in Example 2.1.

Example 2.1: Import Function on Solve Platform

PSS file:

import std_pkg::*;

component pss_top{

      import solve static function bit[64] xor_scramble (bit[64] data, bit[64] seed);

      action perform_scrambling{

           rand bit[64] data;

           rand bit[64] seed;

           bit[64] scrambled_data;

           exec pre_body{

               scrambled_data = xor_scramble(data, seed);

 

               print("Data: %x | Seed: %x \n", data, seed);

               print("Scrambled data: %x \n", scrambled_data);

          };

     };

};

C file:

unsigned long long xor_scramble(unsigned long long data, unsigned long long seed) {

    // In a real implementation, 'seed' would be generated by a more complex PRBS generator

    // For this example, we use a simple shift and XOR for demonstration

    unsigned long long prbs = (seed ^ (seed >> 7) ^ (seed >> 11) ^ (seed >> 31)); // Simplified feedback taps

    unsigned long long scrambled_data = data ^ prbs;

    return scrambled_data;

}

Composer View of Example 2.1: Highlighted Portion of Snapshot Showing Input Values and Output Value After Function Call at Perspec Composer Window

The import solve function xor_scramble is defined with input arguments like 64-bit data and a seed, and returns scrambled data. The xor_scramble function is declared on the Solve platform, and its C-language implementation is already available. This shows that any functionality previously developed in another programming language can be seamlessly reused.

As shown in the above examples, if you want to reuse functionality that already exists in a foreign language and make it available on the target platform within a PSS model, you can achieve this by leveraging exec blocks and using functionality present in separate external libraries. These allow you to integrate and invoke platform-specific implementations, enabling seamless reuse of existing code without rewriting it from scratch.

Annex D of the Portable Stimulus Standard (PSS) LRM has details of type mapping of PSS data types to C/SV.

Conclusion

The ability to import and integrate foreign language logic into PSS on both Target and Solve platforms opens powerful possibilities for code reuse and cross-language collaboration. This feature enables teams to leverage existing solutions written in other languages, reducing duplication of effort and accelerating development. By bridging the gap between PSS and external languages, we can now build a more flexible, scalable, and efficient verification flow. Perspec supports PSS import functions (all of the examples shown in this blog are supported by Perspec).

Learn more about PSS and the Cadence Perspec System Verifier.

© 2026 Cadence Design Systems, Inc. All Rights Reserved.

  • Terms of Use
  • Privacy
  • Cookie Policy
  • US Trademarks
  • Do Not Sell or Share My Personal Information