• 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. PSS 3.1 Channels: The Game Changer for Target-Time Comm…
Siddh Virani
Siddh Virani

Community Member

Blog Activity
Options
  • Subscribe by email
  • More
  • Cancel
PSS 3.1
Perspec
System Design and Verification
channels
perspec system verifier
target time communication

PSS 3.1 Channels: The Game Changer for Target-Time Communication

16 Jul 2026 • 7 minute read

Portable Stimulus models already feature powerful language constructs to describe scenario intent. Activities, constraints, resources, buffers, streams, and states let the Perspec solver build a legal action schedule before the test runs. That is the right layer for planned action relationships: which action produces data, which action consumes it, which operations may run in parallel, and which resources must be shared or locked. Build a legal action schedule before the test runs. That is the right layer for planned action relationships: which action produces data, which action consumes it, which operations may run in parallel, and which resources must be shared or locked.

The tests generated also need to coordinate while they are running. A target-side interrupt may arrive after a device finishes work. A service loop may need to wait for requests from another running exec block. One execution context may need to send several values to another before either action completes. These situations are not just solver scheduling relationships; they are target-time communication between realization code that is already executing.

The upcoming PSS 3.1 channel addresses this gap. A channel is a component type defined inside the core library sync_pkg package; it is designed to carry typed values at the target time. It provides blocking get() and put() methods, nonblocking try_get() and try_put() methods, FIFO ordering up to the declared depth, and thread-safe behavior for concurrently running realization code. PSS 3.1 channels are a standardized feature that replaces a Perspec proprietary implementation known as ps_event.

Why Channels Are Different from Flow Objects

Buffers and streams are scenario-layer flow objects. They make dependencies visible to the solver. A buffer says the producer must finish before consumers start. A stream says producer and consumer actions are scheduled concurrently. These are powerful constructs, but they describe relationships in the solved action graph.

Channels operate inside target execution. An action can remain active, loop, compute a value from target-time state, put that value into a channel, wait for a completion value, and continue. Communication does not need to be exposed as a separate solver-visible flow object for every runtime exchange.

Use flow objects when the relationship should shape the generated scenario. Use channels when the running target code needs FIFO communication or blocking synchronization after execution has started.

Basic Example: Runtime Request and Completion

The following example models a simple client and service that exchange multiple requests and completions while both exec bodies are running. The client sends a request, waits for the matching completion, then sends the next request. The service waits for each request and returns to completion.

import std_pkg::*;
import sync_pkg::*;

const int NUM_REQUESTS = 4;

struct request_s : packed_s<> {
    bit[16] addr;
    bit[8] data;
}

struct completion_s : packed_s<> {
    bit[8] status;
    bit[8] observed_data;
}

component pss_top {
    channel_c<request_s, 2> requests;
    channel_c<completion_s, 2> completions;

    action client {
        exec body {
            repeat (i:NUM_REQUESTS) {
                request_s req;
                completion_s done;

                req.addr = (bit[16])(0x1000 + i);
                req.data = (bit[8])(0x40 + i);

                message(LOW, "client sends request addr=0x%x data=0x%x", req.addr, req.data);
                comp.requests.put(req);

                done = comp.completions.get();
                if (done.status != 0) {
                    error("request failed with status %d", done.status);
                }
                if (done.observed_data != req.data) {
                    error("completion data mismatch expected=0x%x actual=0x%x", req.data, done.observed_data);
                }
            }
        }
    }

    action service {
        exec body {
            repeat (i:NUM_REQUESTS) {
                request_s req;
                completion_s done;

                req = comp.requests.get();
                message(LOW, "service handles request addr=0x%x data=0x%x", req.addr, req.data);

                done.status = 0;
                done.observed_data = req.data;
                comp.completions.put(done);
            }
        }
    }

    action runtime_request_completion {
        activity {
            parallel {
                do client;
                do service;
            }
        }
    }
}

This is not just a producer-consumer stream. A stream would make the producer and consumer relationship part of the solved scenario. Here, the interesting behavior is the target-time request/completion loop inside the running exec bodies. The two actions stay active, exchange several values, and block only when the channel operation requires it. That is the kind of runtime coordination channels were designed to express.

This example was generated successfully with Cadence’s Perspec version 26.06.001 using the below command:

perspec generate -pss runtime_request_completion.pss -top_action pss_top::runtime_request_completion

Interrupt Modeling Is an Important Use Case

Interrupt-driven flows are a common place where channels are useful. A test may start a device operation, continue running target code, and then wait until an interrupt handler confirms that the hardware has completed the operation.

A typical structure is:

import sync_pkg::*;

extend component uart_c {
    channel_c<bit> tx_irq;
    channel_c<bit> rx_irq;

    target function void Isr() {
        uart_ctrl_ua_cisr_reg_s status;
        status = regs.ua_cisr.read();

        if (status.tempty == 1) {
            bool put_status = tx_irq.try_put(1);
            if (put_status == false) {
                message(LOW, "TX interrupt indication could not be queued");
            }
        }

        if (status.rtrig == 1) {
            bool put_status = rx_irq.try_put(1);
            if (put_status == false) {
                message(LOW, "RX interrupt indication could not be queued");
            }
        }
    }

    extend action rx_uart_data {
        exec body {
            bit[32] fifo_data;

            comp.rx_irq.get();
            fifo_data = comp.regs.ua_rfifo.read().data;
            // Check or process the received data here.
        }
    }
}

The ISR uses try_put() because interrupt handlers should avoid blocking when possible. If the channel is full, the return value makes overflow handling explicit. The waiting action uses get() because it should stop at that point until the interrupt indication is available.

This mirrors real interrupt-driven software more naturally than a purely solved dependency. The action is not merely ordered after another action. It is waiting for a runtime condition observed by the target code.

Executors and Target Execution Units

PSS already uses the term executor for a target thread or processing context that can run exec code. A generated test may also have a larger packaging boundary: a target program or image that contains one or more executors. In the current Perspec flows this packaging boundary is called an executable. In the upcoming PSS 3.1 terminology, this concept is expected to be called a target execution unit.

This distinction matters for channels because target-time communication may happen between exec blocks that are placed in different ways:

  • On the same executor,
  • On different executors in the same target execution unit,
  • Or on executors that belong to different target execution units.

The PSS source code should not need to change for each placement. The model says put(), get(), try_put(), or try_get() on a channel. The tool then chooses the appropriate generated implementation for the target platform.

When both sides of the channel are local to the same generated C image, the implementation can use local generated storage and a local mailbox-style synchronization mechanism. When the channel crosses target execution units, the implementation must use a communication mechanism both sides can access. In the HSI example, Perspec generated two C files:

One C file runs on the simulation side and drives the SystemVerilog environment through DPI, while another embedded C file executes on the OpenRISC RTL.

For channel communication that crosses that boundary, Perspec generated a shared memory mailbox plus a doorbell-style notification mechanism.

This is one of the main benefits of channels. The model captures the target-time communication intent once, while the tool handles whether the generated implementation is local, shared-memory based, or mapped to another platform-specific synchronization mechanism.

When to Use Channels

Use channels when:

  • Communication happens while exec code is running.
  • The producer and consumer are long-running target-side behaviors rather than single solved action endpoints.
  • Runtime code needs FIFO ordering with a declared depth.
  • Blocking or nonblocking synchronization is needed between concurrently running realization code.
  • An interrupt handler or callback needs to notify model code that is waiting at the target time.

Do not use channels just to replace flow objects. If the dependency should be visible to the solver, use buffers, streams, states, resources, and constraints. Channels are for target-time communication in the realization layer.

Conclusion

PSS 3.1 channels add a standard way to describe target-time communication between running realization code. They complement, rather than replace, solver scheduling and flow objects. Flow objects shape the scenario before execution; channels coordinate execution after the generated test is already running.

Interrupt modeling is one of the most common uses, but it is not the only one. Request/completion loops, service-style target code, callbacks, and communication across execution contexts all benefit from the same abstraction. Channels let the model express the intent portably while leaving the implementation details to the PSS tool and target platform. While PSS LRM 3.1 is expected to be officially published in October–November 2026, users can start leveraging channel functionality today with Perspec version 26.06.001.

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

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