UPF concept and examples (Single voltage domain, multi-voltage domain, power switch, isolation and level shifter, and retention)

July 13, 2026

Universal Power Format (UPF) was created to allow EDA tools to identify any voltage mismatch problem areas due to multi-voltage domains that came about due to low voltage domains of various IPs and even core voltage. Legacy IPs like PLL may operate from higher voltage domain such as 1.8V but core voltage domain may operate at 1.0V. This mismatched voltage domain could cause semiconductor issues if no voltage translators are inserted. And additionally, if power domains are shutoff, there must be isolation cells inserted to not cause reverse diodes to trigger aka latchup.

Power switch is to shutoff entire voltage domain

Isolation is to not leave outputs of a shutdown voltage domain to float as this would cause the semiconductor chip to latchup.

Level shifter cells are inserted to translate from one voltage domain to another, whether it is down voltage level shifting or up voltage shifting.

Retention cells are to keep certain flip-flops powered to retain certain states and conditions so that upon exiting sleep modes the SoC can be powered up in last known state.

Single Voltage Domain Example

# ==============================================================================

# UPF v2.0/v2.1 Example: Single Voltage Domain

# #==============================================================================

## 1. Set the UPF Scope

## Sets the current scope to the top-level design module

set_scope /

## 2. Create Supply Ports

## These represent the physical power and ground pins at the design boundary

create_supply_port VDD
create_supply_port VSS

## 3. Create Supply Nets

## These are the internal wires carrying the power and ground signals

create_supply_net VDD
create_supply_net VSS

## 4. Connect Ports to Nets

## Connects the top-level pins to our internal supply wires

connect_supply_net VDD -ports VDD
connect_supply_net VSS -ports VSS

## 5. Create the Primary Power Domain

## For a single-voltage chip, the entire design belongs to one default domain

create_power_domain PD_TOP -include_scope

## 6. Update the Power Domain with Supply Nets

## Assigns the VDD and VSS nets as the primary power and ground for this domain

set_domain_supply_net PD_TOP \
-primary_power_net VDD \
-primary_ground_net VSS

## 7. Define the Set-State (Power States)

## Tells the tools what voltage levels to expect during analysis

add_port_state VDD -state {ON 0.90}
add_port_state VSS -state {GND 0.00}

## 8. Create the Power State Table (PST)

## Maps out the legal combinations of states (only 1 valid state here)

create_pst top_pst -supplies {VDD VSS}
add_pst_state state_on -pst top_pst -state {ON GND}

What this code does under the hood

  • create_power_domain PD_TOP -include_scope: This tells the tool that every single cell, macro, and sub-block from the top level down belongs to this domain (PD_TOP).
  • set_domain_supply_net: This acts like the main power grid connection, hooking up the primary supplies to every standard cell in the design.
  • add_port_state & create_pst: While redundant for a chip that never turns off, defining a Power State Table (PST) is a strict requirement for many EDA implementation and formal verification tools to validate that your voltage levels are always consistent.

UPF Example : Power-Gated Domain with Power Switch Cell

# ==============================================================================

# UPF Example: Power-Gated Domain with Power Switch Cell

# ==============================================================================

set_scope /

## 1. Create Supply Ports (Always-On Global Supplies)

create_supply_port VDD
create_supply_port VSS

## 2. Create Supply Nets

create_supply_net VDD ; # Always-On main supply
create_supply_net VSS ; # Common ground
create_supply_net VDD_switched ; # Switched (gated) power rail for the power domain

## 3. Connect Ports to Global Nets

connect_supply_net VDD -ports VDD
connect_supply_net VSS -ports VSS

## 4. Define Power Domains

## Define the top-level always-on domain

create_power_domain PD_TOP -include_scope

## Define the gated domain (e.g., a block that turns off to save leakage power)

## Replace ‘u_switched_block’ with your actual hierarchical instance path

create_power_domain PD_GATED -elements {u_switched_block}

## 5. Assign Primary Supplies to Domains

set_domain_supply_net PD_TOP \
-primary_power_net VDD \
-primary_ground_net VSS

set_domain_supply_net PD_GATED \
-primary_power_net VDD_switched \
-primary_ground_net VSS

## 6. Create and Configure the Power Switch

## This instantiates the logic for the power switch cell mapping

create_power_switch my_power_switch \
-domain PD_GATED \
-input_supply_port {VDD_in VDD} \
-output_supply_port {VDD_out VDD_switched} \
-control_port {pwr_ena pwr_ctrl_net} \
-on_state {SWITCH_ON VDD_in {pwr_ena}}

## Note: ‘pwr_ctrl_net’ must match the net name of your power management unit’s

## switch control signal in the RTL logic.

## 7. Define Port States

add_port_state VDD -state {ON 0.90}
add_port_state VDD_switched -state {ON 0.90} -state {OFF off}
add_port_state VSS -state {GND 0.00}

## 8. Create the Power State Table (PST)

## The PST now accounts for both the fully awake and power-gated modes

create_pst top_pst -supplies {VDD VDD_switched VSS}
add_pst_state state_run -pst top_pst -state {ON ON GND}
add_pst_state state_gate -pst top_pst -state {ON OFF GND}

Key Adjustments Explained

  • VDD_switched Net: This is the virtual power rail. It only connects to the standard cells inside the PD_GATED domain.
  • create_power_switch: This is the magic command.
    • -input_supply_port hooks up to the always-on VDD.
    • -output_supply_port hooks up to the virtual VDD_switched.
    • -control_port binds the physical switch enable pin to the RTL net (pwr_ctrl_net) driving the shutdown sequence.
    • -on_state specifies that when pwr_ena is active (typically high/1, depending on your cell library’s design), the switch closes and passes VDD_in through.
  • PST Update: The Power State Table now validates two legal operating states (state_run and state_gate), allowing implementation tools to properly analyze cross-domain paths and check if isolation cells are required when signals leave the gated domain.

UPF Multi-Voltage, Multi-Domain design

To scale this up to a Multi-Voltage, Multi-Domain design, we need to introduce a second independent voltage supply rail (VDD2).

This configuration establishes two distinct switched power domains (PD_GATED_A and PD_GATED_B). Each domain is powered by a different voltage level and is controlled by its own dedicated power switch cell.

#==============================================================================

#UPF Example: Multi-Voltage with Dual Power-Gated Domains

#==============================================================================

set_scope /

# 1. Create Supply Ports (Two independent power rails + common ground)

create_supply_port VDD1 ; # Primary rail (e.g., 0.90V)
create_supply_port VDD2 ; # Secondary rail (e.g., 0.75V)
create_supply_port VSS

# 2. Create Supply Nets

create_supply_net VDD1
create_supply_net VDD2
create_supply_net VSS
create_supply_net VDD1_switched ; # Gated rail for Module A
create_supply_net VDD2_switched ; # Gated rail for Module B

# 3. Connect Ports to Global Nets

connect_supply_net VDD1 -ports VDD1
connect_supply_net VDD2 -ports VDD2
connect_supply_net VSS -ports VSS

# 4. Define Power Domains

create_power_domain PD_TOP -include_scope
create_power_domain PD_GATED_A -elements {u_mod_a}
create_power_domain PD_GATED_B -elements {u_mod_b}

# 5. Assign Primary Supplies to Domains

set_domain_supply_net PD_TOP
-primary_power_net VDD1
-primary_ground_net VSS

set_domain_supply_net PD_GATED_A
-primary_power_net VDD1_switched
-primary_ground_net VSS

set_domain_supply_net PD_GATED_B
-primary_power_net VDD2_switched
-primary_ground_net VSS

# 6. Instantiate Power Switch 1 (Module A – tied to VDD1)

create_power_switch switch_mod_a
-domain PD_GATED_A
-input_supply_port {VDD_in VDD1}
-output_supply_port {VDD_out VDD1_switched}
-control_port {pwr_ena pwr_ctrl_a}
-on_state {SWITCH_ON VDD_in {pwr_ena}}

# 7. Instantiate Power Switch 2 (Module B – tied to VDD2)

create_power_switch switch_mod_b
-domain PD_GATED_B
-input_supply_port {VDD_in VDD2}
-output_supply_port {VDD_out VDD2_switched}
-control_port {pwr_ena pwr_ctrl_b}
-on_state {SWITCH_ON VDD_in {pwr_ena}}

# 8. Define Port States (Declaring different voltage operating points)

add_port_state VDD1 -state {ON_0p90 0.90}
add_port_state VDD1_switched -state {ON_0p90 0.90} -state {OFF off}

add_port_state VDD2 -state {ON_0p75 0.75}
add_port_state VDD2_switched -state {ON_0p75 0.75} -state {OFF off}

add_port_state VSS -state {GND 0.00}

# 9. Create the Power State Table (PST)

# Captures all valid permutations of operational and power-gated states

create_pst top_pst -supplies {VDD1 VDD1_switched VDD2 VDD2_switched VSS}

# Both blocks active

add_pst_state ALL_RUN -pst top_pst -state {ON_0p90 ON_0p90 ON_0p75 ON_0p75 GND}

# Module A gated, Module B running

add_pst_state GATE_A -pst top_pst -state {ON_0p90 OFF ON_0p75 ON_0p75 GND}

# Module A running, Module B gated

add_pst_state GATE_B -pst top_pst -state {ON_0p90 ON_0p90 ON_0p75 OFF GND}

# Both blocks gated (Deep Sleep)

add_pst_state ALL_GATE -pst top_pst -state {ON_0p90 OFF ON_0p75 OFF GND}

Critical Verification Checklist for this Setup

Since you are dealing with different voltages alongside power gating, synthesis and verification tools (like SpyGlass CDC/LP or VC LP) will look for two additional architectural structures:

  1. Level Shifters: Required for signals crossing directly between PD_TOP/PD_GATED_A (0.90V) and PD_GATED_B (0.75V) to handle the voltage differential.
  2. Isolation Cells: Required on any output signal leaving PD_GATED_A or PD_GATED_B. When either domain is switched off, its output pins will float, which can cause severe short-circuit current (crowbar current) in downstream always-on logic if not clamped to a constant high or low state.

UPF Add Isolation Cells and Level Shifter Cells

# ==============================================================================

# UPF Example: Dual Gated Domains + Isolation + Level Shifters

# ==============================================================================

set_scope /

# 1. Create Supply Ports & Nets

create_supply_port VDD1
create_supply_port VDD2
create_supply_port VSS

create_supply_net VDD1
create_supply_net VDD2
create_supply_net VSS
create_supply_net VDD1_switched
create_supply_net VDD2_switched

# 2. Connect Ports to Global Nets

connect_supply_net VDD1 -ports VDD1
connect_supply_net VDD2 -ports VDD2
connect_supply_net VSS -ports VSS

# 3. Define Power Domains

create_power_domain PD_TOP -include_scope
create_power_domain PD_GATED_A -elements {u_mod_a}
create_power_domain PD_GATED_B -elements {u_mod_b}

# 4. Assign Primary Supplies to Domains

set_domain_supply_net PD_TOP -primary_power_net VDD1 -primary_ground_net VSS
set_domain_supply_net PD_GATED_A -primary_power_net VDD1_switched -primary_ground_net VSS
set_domain_supply_net PD_GATED_B -primary_power_net VDD2_switched -primary_ground_net VSS

# 5. Instantiate Power Switches

create_power_switch switch_mod_a \
-domain PD_GATED_A \
-input_supply_port {VDD_in VDD1} \
-output_supply_port {VDD_out VDD1_switched} \
-control_port {pwr_ena pwr_ctrl_a} \
-on_state {SWITCH_ON VDD_in {pwr_ena}}

create_power_switch switch_mod_b \
-domain PD_GATED_B \
-input_supply_port {VDD_in VDD2} \
-output_supply_port {VDD_out VDD2_switched} \
-control_port {pwr_ena pwr_ctrl_b} \
-on_state {SWITCH_ON VDD_in {pwr_ena}}

#==============================================================================

#NEW: 6. Isolation Strategies

#==============================================================================

# Isolation for Module A (Clamps outputs to ‘0’ when gated)

set_isolation iso_mod_a \
-domain PD_GATED_A \
-isolation_power_net VDD1 \
-isolation_ground_net VSS \
-clamp_value 0 \
-applies_to outputs

set_isolation_control iso_mod_a \
-domain PD_GATED_A \
-isolation_signal iso_ctrl_a \
-isolation_sense low \
-location parent

# Isolation for Module B (Clamps outputs to ‘1’ when gated)

set_isolation iso_mod_b \
-domain PD_GATED_B \
-isolation_power_net VDD2 \
-isolation_ground_net VSS \
-clamp_value 1 \
-applies_to outputs

set_isolation_control iso_mod_b \
-domain PD_GATED_B \
-isolation_signal iso_ctrl_b \
-isolation_sense low \
-location parent

#==============================================================================

#NEW: 7. Level Shifter Strategies

#==============================================================================

# Strategy for signals moving from PD_TOP/PD_GATED_A (0.90V) down to PD_GATED_B (0.75V)

set_level_shifter ls_high_to_low
-domain PD_GATED_B
-source PD_TOP
-rule high_to_low
-location self

# Strategy for signals moving from PD_GATED_B (0.75V) up to PD_TOP/PD_GATED_A (0.90V)

set_level_shifter ls_low_to_high
-domain PD_GATED_B
-source PD_GATED_B
-rule low_to_high
-location parent

#==============================================================================

#8. Define Port States & Power State Table (PST)

#==============================================================================

add_port_state VDD1 -state {ON_0p90 0.90}
add_port_state VDD1_switched -state {ON_0p90 0.90} -state {OFF off}
add_port_state VDD2 -state {ON_0p75 0.75}
add_port_state VDD2_switched -state {ON_0p75 0.75} -state {OFF off}
add_port_state VSS -state {GND 0.00}

create_pst top_pst -supplies {VDD1 VDD1_switched VDD2 VDD2_switched VSS}
add_pst_state ALL_RUN -pst top_pst -state {ON_0p90 ON_0p90 ON_0p75 ON_0p75 GND}
add_pst_state GATE_A -pst top_pst -state {ON_0p90 OFF ON_0p75 ON_0p75 GND}
add_pst_state GATE_B -pst top_pst -state {ON_0p90 ON_0p90 ON_0p75 OFF GND}
add_pst_state ALL_GATE -pst top_pst -state {ON_0p90 OFF ON_0p75 OFF GND}

Implementation Details to Keep in Mind

Isolation Strategy Details

  • -isolation_power_net: Must be connected to a supply that stays ON when the domain itself is gated. For PD_GATED_A, we power the isolation cells using the global always-on VDD1.
  • -location parent: Places the isolation cell outside the gated block (in the parent domain). This ensures the isolation cell itself doesn’t lose power when the domain shuts down.
  • -isolation_sense low: Means isolation activates when iso_ctrl drops to 0. (Always ensure your RTL Power Management Unit asserts isolation before pulling down the power switch enable).

Level Shifter Details

  • -rule: Explicitly states the voltage transition direction (high_to_low or low_to_high). Implementation tools read the PST to see that VDD1 is $0.90\text{V}$ and VDD2 is $0.75\text{V}$ to accurately map the physical standard cells.
  • -location self vs parent: Controls where the level shifter cell is physically placed in the layout hierarchy to guarantee it always has access to both the source and destination power rails required to perform the shift.

UPF Retention Cell Example

To keep the critical internal state (like a configuration register or program counter) intact when your gated domains power down, you need to implement Retention Cells.

Retention flip-flops feature an extra “shadow latch” that is hooked up to an always-on power supply. When the main power rail goes dead, the data is preserved in this backup latch and automatically restored when power returns.

#==============================================================================

#UPF Example: Dual Gated Domains + Isolation + Level Shifters + Retention

#==============================================================================

set_scope /

#1. Create Supply Ports & Nets

create_supply_port VDD1
create_supply_port VDD2
create_supply_port VSS

create_supply_net VDD1
create_supply_net VDD2
create_supply_net VSS
create_supply_net VDD1_switched
create_supply_net VDD2_switched

#2. Connect Ports to Global Nets

connect_supply_net VDD1 -ports VDD1
connect_supply_net VDD2 -ports VDD2
connect_supply_net VSS -ports VSS

#3. Define Power Domains

create_power_domain PD_TOP -include_scope
create_power_domain PD_GATED_A -elements {u_mod_a}
create_power_domain PD_GATED_B -elements {u_mod_b}

#4. Assign Primary Supplies to Domains

set_domain_supply_net PD_TOP -primary_power_net VDD1 -primary_ground_net VSS
set_domain_supply_net PD_GATED_A -primary_power_net VDD1_switched -primary_ground_net VSS
set_domain_supply_net PD_GATED_B -primary_power_net VDD2_switched -primary_ground_net VSS

#5. Instantiate Power Switches

create_power_switch switch_mod_a \
-domain PD_GATED_A \
-input_supply_port {VDD_in VDD1} \
-output_supply_port {VDD_out VDD1_switched} \
-control_port {pwr_ena pwr_ctrl_a} \
-on_state {SWITCH_ON VDD_in {pwr_ena}}

create_power_switch switch_mod_b \
-domain PD_GATED_B \
-input_supply_port {VDD_in VDD2} \
-output_supply_port {VDD_out VDD2_switched} \
-control_port {pwr_ena pwr_ctrl_b} \
-on_state {SWITCH_ON VDD_in {pwr_ena}}

#==============================================================================

#NEW: 6. Retention Strategy (For Module A)

#==============================================================================

#Step A: Define WHICH registers should be retained

#You can use -instance to pinpoint specific registers, or target all sequential cells.

set_retention ret_mod_a \
-domain PD_GATED_A \
-retention_power_net VDD1 \
-retention_ground_net VSS \
-instance {u_mod_a/u_regs/state_reg_reg u_mod_a/u_regs/conf_reg[*]}

#Step B: Map the control signals that trigger the save/restore mechanism

set_retention_control ret_mod_a
-domain PD_GATED_A
-save_signal {ret_save_a high}
-restore_signal {ret_rest_a low}

#Note: Modern standard cell libraries often feature ‘balloon’ latches that use a single

#control pin (e.g., ‘SAVE_RESTORE_N’). If your library uses a unified control pin,

#you can use the unified -assertion_signal parameter instead of separate save/restore pins.

#==============================================================================

#7. Isolation Strategies

#==============================================================================

set_isolation iso_mod_a \
-domain PD_GATED_A \
-isolation_power_net VDD1 \
-isolation_ground_net VSS \
-clamp_value 0 \
-applies_to outputs

set_isolation_control iso_mod_a \
-domain PD_GATED_A \
-isolation_signal iso_ctrl_a \
-isolation_sense low \
-location parent

set_isolation iso_mod_b \
-domain PD_GATED_B \
-isolation_power_net VDD2 \
-isolation_ground_net VSS \
-clamp_value 1 \
-applies_to outputs

set_isolation_control iso_mod_b \
-domain PD_GATED_B \
-isolation_signal iso_ctrl_b \
-isolation_sense low \
-location parent

#==============================================================================

#8. Level Shifter Strategies

#==============================================================================

set_level_shifter ls_high_to_low \
-domain PD_GATED_B \
-source PD_TOP \
-rule high_to_low \
-location self

set_level_shifter ls_low_to_high \
-domain PD_GATED_B \
-source PD_GATED_B \
-rule low_to_high \
-location parent

#==============================================================================

#9. Define Port States & Power State Table (PST)

#==============================================================================

add_port_state VDD1 -state {ON_0p90 0.90}
add_port_state VDD1_switched -state {ON_0p90 0.90} -state {OFF off}
add_port_state VDD2 -state {ON_0p75 0.75}
add_port_state VDD2_switched -state {ON_0p75 0.75} -state {OFF off}
add_port_state VSS -state {GND 0.00}

create_pst top_pst -supplies {VDD1 VDD1_switched VDD2 VDD2_switched VSS}
add_pst_state ALL_RUN -pst top_pst -state {ON_0p90 ON_0p90 ON_0p75 ON_0p75 GND}
add_pst_state GATE_A -pst top_pst -state {ON_0p90 OFF ON_0p75 ON_0p75 GND}
add_pst_state GATE_B -pst top_pst -state {ON_0p90 ON_0p90 ON_0p75 OFF GND}
add_pst_state ALL_GATE -pst top_pst -state {ON_0p90 OFF ON_0p75 OFF GND}

AXI4 Lite to APB Bridge or Converter

July 10, 2026

APB Protocol Background

APB description

The basic interface to APB (Advanced Peripheral Bus) consists of these two input signals to generate the request : PSELx and PENABLE. These control the APB state machine to transfer the read and write data. To limit the write transfers, transactions can be held off to be single transactions with the output of the peripheral as PREADY. When PREADY =1 , the peripheral can accept transactions and when PREADY=0, then peripheral will NOT accept transactions. It also breaks transfers into a simple 2-cycle process (a setup phase and an access phase). Other APB signals like PWRITE are driven by the converter from AXI interface. Note that once the APB slave peripheral like a memory has accepted the transfer it will drop the PREADY=0 to accept next transfer.

Key Design Details:

  • AXI4-Lite Support: Optimized for the lightweight protocol, allowing single-beat, non-bursting transactions where the AR or AW channels lock the bus until complete.
  • APB Protocol Alignment: Uses the two-cycle sequence dictated by APB; cycle 1 (SETUP with PSEL high and PENABLE low), followed by cycle 2 (ACCESS with PSEL high and PENABLE high).
  • Wait State Handling: The ACCESS state waits dynamically on the peripheral by checking m_apb_pready, stalling the AXI response until the peripheral is finished with current single-beat operation.

APB Finite State Machine

APB Example Waveform

The waveform below illustrates this simple APB protocol for a write and read cycle.

Write Transaction (Cycles 2 to 5):

SETUP Phase (Cycle 2): PSELx goes high, PWRITE is high (Write operation), and the address/data buses are driven. PENABLE stays low.

ACCESS Phase – Wait State (Cycle 3): PENABLE goes high. The slave isn’t ready yet, so PREADY stays low. The FSM holds this state.

ACCESS Phase – Complete (Cycle 4): The slave drives PREADY high, indicating it has successfully sampled PWDATA.

IDLE Phase (Cycle 5): The transfer finishes; PSELx and PENABLE drop back to low.

Read Transaction (Cycles 6 to 8):

SETUP Phase (Cycle 6): PSELx goes high, and PWRITE goes low to signal a read command.

ACCESS Phase (Cycle 7): PENABLE goes high. In this cycle, the slave’s internal counter allows it to respond immediately, driving PREADY high and placing 0xDEADBEEF onto PRDATA.

Return to IDLE (Cycle 8): The master captures PRDATA on the rising edge, and the bus returns to idle.

Systemverilog code for AXI4 Lite to APB Converter

We start with the AXI4 Lite slave interface signals since it is a target which is driven by an AXI4 master. So most signals are input except for the *ready which are outputs back to the AXI master to control data transfers.

The systemverilog uses a typedef to define the APB Finite State Machine states : {IDLE SETUP,ACCESS}

AXI has 5 channels : AW, W (write data), B (wr response), AR, R (read data)

APB has one channel for both write and read with signals :

outputs : PADDR, PWRITE (write=1, read=0), PSELx, PENABLE, PWDATA

inputs : PRDATA, PREADY, PSLVERR

module axi_lite_to_apb #(
parameter integer AXI_ADDR_WIDTH = 32,
parameter integer AXI_DATA_WIDTH = 32,
parameter integer APB_ADDR_WIDTH = 32,
parameter integer APB_DATA_WIDTH = 32
) (
// System Signals
input logic clk,
input logic rst_n,

// AXI4-Lite Slave Interface (Write Address Channel)
input logic [AXI_ADDR_WIDTH-1:0] s_axi_awaddr,
input logic [2:0] s_axi_awprot,
input logic s_axi_awvalid,
output logic s_axi_awready,
// AXI4-Lite Slave Interface (Write Data Channel)
input logic [AXI_DATA_WIDTH-1:0] s_axi_wdata,
input logic [(AXI_DATA_WIDTH/8)-1:0] s_axi_wstrb,
input logic s_axi_wvalid,
output logic s_axi_wready,
// AXI4-Lite Slave Interface (Write Response Channel)
output logic [1:0] s_axi_bresp,
output logic s_axi_bvalid,
input logic s_axi_bready,
// AXI4-Lite Slave Interface (Read Address Channel)
input logic [AXI_ADDR_WIDTH-1:0] s_axi_araddr,
input logic [2:0] s_axi_arprot,
input logic s_axi_arvalid,
output logic s_axi_arready,
// AXI4-Lite Slave Interface (Read Data Channel)
output logic [AXI_DATA_WIDTH-1:0] s_axi_rdata,
output logic [1:0] s_axi_rresp,
output logic s_axi_rvalid,
input logic s_axi_rready,
// APB Master Interface
output logic [APB_ADDR_WIDTH-1:0] m_apb_paddr,
output logic m_apb_pwrite,
output logic m_apb_pselx,
output logic m_apb_penable,
output logic [APB_DATA_WIDTH-1:0] m_apb_pwdata,
input logic [APB_DATA_WIDTH-1:0] m_apb_prdata,
input logic m_apb_pready,
input logic m_apb_pslverr

);

// APB State Machine states are defined using enum and specified as datatype
typedef enum logic [1:0] {
IDLE = 2'b00,
SETUP = 2'b01,
ACCESS= 2'b10
} apb_state_t;
// current_state and next_state are defined using this "apb_state_t" type
apb_state_t current_state, next_state;
// Internal Registers for AXI-to-APB Transaction Tracking
logic [AXI_ADDR_WIDTH-1:0] paddr_reg;
logic [AXI_DATA_WIDTH-1:0] pwdata_reg;
logic pwrite_reg;
logic transaction_active;
logic is_read_transaction;
// Internal AXI-Lite Handshake Trackers
logic aw_done, w_done, b_done, ar_done, r_done;
// AXI Ready/Valid Handshakes
assign s_axi_awready = !transaction_active && !is_read_transaction;
assign s_axi_wready = !transaction_active && !is_read_transaction;
assign s_axi_arready = !transaction_active && !is_read_transaction;
// create internal done or transfer operation: if aw_done then latch wr addr, if w_done, latch wr data
// if ar_done, latch rd addr
assign aw_done = s_axi_awvalid && s_axi_awready;
assign w_done = s_axi_wvalid && s_axi_wready;
assign ar_done = s_axi_arvalid && s_axi_arready;
// Capture Write/Read Address and Data
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
transaction_active <= 1'b0;
is_read_transaction <= 1'b0;
paddr_reg <= '0;
pwdata_reg <= '0;
pwrite_reg <= 1'b0;
end else begin
if (!transaction_active) begin
// if transaction previously was NOT active and axi wr is done then transfer awaddr to paddr and set pwrite
if (aw_done) begin
transaction_active <= 1'b1;
is_read_transaction <= 1'b0;
paddr_reg <= s_axi_awaddr;
pwrite_reg <= 1'b1;
// else if axi read is done then transfer araddr to paddr and set pwrite=0 (read)
end else if (ar_done) begin
transaction_active <= 1'b1;
is_read_transaction <= 1'b1;
paddr_reg <= s_axi_araddr;
pwrite_reg <= 1'b0;
end
// else if curren_state=ACCESS and apb peripheral has signaled it's ready to accept new transfer
// then set current transaction has INACTIVE now.
end else if (current_state == ACCESS && m_apb_pready) begin
transaction_active <= 1'b0; // Transaction completes in ACCESS
end
// if w_done is active then transfer axi wdata to APB pwdata
if (w_done) begin
pwdata_reg <= s_axi_wdata;
end
end
end
// APB State Machine FSM - Sequential
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
// APB State Machine FSM - Combinational
always_comb begin
next_state = current_state;
case (current_state)
IDLE: begin // if AXI transaction is in progress move APB FSM to SETUP state
if (transaction_active) begin
next_state = SETUP;
end
end
SETUP: begin // transition to next state ACCESS
next_state = ACCESS;
end
// if APB pready=1 peripheral completed it's transfer and ready to accept next transfer
// so return to idle
ACCESS: begin
if (m_apb_pready) begin
next_state = IDLE;
end else begin
next_state = ACCESS;
end
end
default: next_state = IDLE;
endcase
end
//////////////////////// Drive APB Signals on m_apb_* from State Machine /////////////////////
//////////// since the bridge is considered the master and APB peripheral is the slave ///////
always_comb begin
m_apb_pselx = 1'b0;
m_apb_penable = 1'b0;
m_apb_paddr = '0;
m_apb_pwrite = 1'b0; // default is READ so there is NO accidental WRITE during reset
m_apb_pwdata = '0;
case (current_state)
// SETUP state so output PSELx=1 and PENABLE=0 so peripheral can also transition to SETUP state
SETUP: begin
m_apb_pselx = 1'b1;
m_apb_penable = 1'b0;
m_apb_paddr = paddr_reg;
m_apb_pwrite = pwrite_reg;
m_apb_pwdata = pwdata_reg;
end
// ACCESS state so output PSELx=1 and PENABLE=1 so peripheral can also transition to ACCESS state
ACCESS: begin
m_apb_pselx = 1'b1;
m_apb_penable = 1'b1;
m_apb_paddr = paddr_reg;
m_apb_pwrite = pwrite_reg;
m_apb_pwdata = pwdata_reg;
end
// DEFAULT : PSELx=0 and PENABLE=0 don't do anything
default: begin
m_apb_pselx = 1'b0;
m_apb_penable = 1'b0;
end
endcase
end
// AXI Write Response Generation
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
s_axi_bvalid <= 1'b0;
s_axi_bresp <= 2'b00;
end else begin
// for APB ACCESS state and apb pready and pwrite state return on AXI wr response
if (current_state == ACCESS && m_apb_pready && pwrite_reg) begin
s_axi_bvalid <= 1'b1;
s_axi_bresp <= m_apb_pslverr ? 2'b10 : 2'b00; // DECERR/OKAY mapping
end else if (s_axi_bready) begin
s_axi_bvalid <= 1'b0;
end
end
end
// AXI Read Data Generation
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
s_axi_rvalid <= 1'b0;
s_axi_rdata <= '0;
s_axi_rresp <= 2'b00;
end else begin
if (current_state == ACCESS && m_apb_pready && !pwrite_reg) begin
s_axi_rvalid <= 1'b1;
s_axi_rdata <= m_apb_prdata;
s_axi_rresp <= m_apb_pslverr ? 2'b10 : 2'b00;
end else if (s_axi_rready) begin
s_axi_rvalid <= 1'b0;
end
end
end

endmodule

Testbench for AXI4 Lite to APB Converter with SVA (Systemverilog Assertion)

Expected Write Waveform Progression for mock peripheral memory

  1. AXI Entry: s_axi_awvalid & s_axi_wvalid drive high. The bridge asserts s_axi_awready & s_axi_wready, absorbing the payload.
  2. APB Setup Cycle: m_apb_pselx spikes to 1 while m_apb_penable remains at 0. m_apb_paddr and m_apb_pwdata present their target values.
  3. APB Access Cycle: The subsequent cycle forces m_apb_penable to 1. If your randomized peripheral wait cycles kick in, m_apb_pready lingers at 0, forcing the bridge to extend this step safely.
  4. Completion: Once m_apb_pready matches at 1, m_apb_penable and m_apb_pselx safely switch off on the next clock, while s_axi_bvalid shoots high to let the master know the sequence wrapped up safely.

`timescale 1ns/1ps

module tb_axi_lite_to_apb;

// Parameters
localparam ADDR_WIDTH = 32;
localparam DATA_WIDTH = 32;
localparam CLK_PERIOD = 10;
// Clock and Reset
logic clk;
logic rst_n;
// AXI Lite Signals
logic [ADDR_WIDTH-1:0] s_axi_awaddr;
logic [2:0] s_axi_awprot;
logic s_axi_awvalid;
logic s_axi_awready;
logic [DATA_WIDTH-1:0] s_axi_wdata;
logic [(DATA_WIDTH/8)-1:0] s_axi_wstrb;
logic s_axi_wvalid;
logic s_axi_wready;
logic [1:0] s_axi_bresp;
logic s_axi_bvalid;
logic s_axi_bready;
logic [ADDR_WIDTH-1:0] s_axi_araddr;
logic [2:0] s_axi_arprot;
logic s_axi_arvalid;
logic s_axi_arready;
logic [DATA_WIDTH-1:0] s_axi_rdata;
logic [1:0] s_axi_rresp;
logic s_axi_rvalid;
logic s_axi_rready;
// APB Signals
logic [ADDR_WIDTH-1:0] m_apb_paddr;
logic m_apb_pwrite;
logic m_apb_pselx;
logic m_apb_penable;
logic [DATA_WIDTH-1:0] m_apb_pwdata;
logic [DATA_WIDTH-1:0] m_apb_prdata;
logic m_apb_pready;
logic m_apb_pslverr;
// Instantiate Design Under Test (DUT)
axi_lite_to_apb #(
.AXI_ADDR_WIDTH(ADDR_WIDTH),
.AXI_DATA_WIDTH(DATA_WIDTH),
.APB_ADDR_WIDTH(ADDR_WIDTH),
.APB_DATA_WIDTH(DATA_WIDTH)
) dut (.*);
// Clock Generation
always #(CLK_PERIOD/2) clk = ~clk;
// Simulated APB Peripheral Model (Dynamic Response)
logic [DATA_WIDTH-1:0] mock_peripheral_memory [logic [7:0]];
logic [1:0] apb_wait_cycles;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
m_apb_pready <= 1'b0;
m_apb_prdata <= '0;
m_apb_pslverr <= 1'b0;
apb_wait_cycles <= 0;
end else begin
if (m_apb_pselx && !m_apb_penable) begin
// Randomize wait cycles (0 to 2 wait states) to stress-test ready logic
apb_wait_cycles <= $urandom_range(0, 2);
m_apb_pready <= 1'b0;
end else if (m_apb_pselx && m_apb_penable) begin
if (apb_wait_cycles > 0) begin
apb_wait_cycles <= apb_wait_cycles - 1;
end else begin
m_apb_pready <= 1'b1;
if (m_apb_pwrite) begin
mock_peripheral_memory[m_apb_paddr[7:0]] <= m_apb_pwdata;
end else begin
if (mock_peripheral_memory.exists(m_apb_paddr[7:0])) begin
m_apb_prdata <= mock_peripheral_memory[m_apb_paddr[7:0]];
end else begin
m_apb_prdata <= 32'hDEADBEEF; // Default read value
end
end
end
end else begin
m_apb_pready <= 1'b0;
end
end
end
// Stimulus Generation Tasks
task init_signals();
s_axi_awaddr = '0;
s_axi_awprot = '0;
s_axi_awvalid = 1'b0;
s_axi_wdata = '0;
s_axi_wstrb = '1;
s_axi_wvalid = 1'b0;
s_axi_bready = 1'b0;
s_axi_araddr = '0;
s_axi_arprot = '0;
s_axi_arvalid = 1'b0;
s_axi_rready = 1'b0;
endtask
task reset_dut();
rst_n = 1'b1;
#(CLK_PERIOD * 0.2);
rst_n = 1'b0;
#(CLK_PERIOD * 2);
rst_n = 1'b1;
#(CLK_PERIOD);
endtask
task axi_write(input [ADDR_WIDTH-1:0] addr, input [DATA_WIDTH-1:0] data);
$display("[%0t ns] [AXI WRITE START] Addr: 0x%h, Data: 0x%h", $time, addr, data);
// Assert Address and Data Valid simultaneously (common in AXI-Lite)
s_axi_awaddr = addr;
s_axi_awvalid = 1'b1;
s_axi_wdata = data;
s_axi_wvalid = 1'b1;
s_axi_bready = 1'b1;
// Wait for both address and data configurations to accept
fork
begin
while (!s_axi_awready) @(posedge clk);
@(posedge clk);
s_axi_awvalid = 1'b0;
end
begin
while (!s_axi_wready) @(posedge clk);
@(posedge clk);
s_axi_wvalid = 1'b0;
end
join
// Wait for Write Response
while (!s_axi_bvalid) @(posedge clk);
// Immediate Assertion check for response code
a_write_resp_ok: assert(s_axi_bresp == 2'b00)
else $error("AXI Write response error encountered!");
@(posedge clk);
s_axi_bready = 1'b0;
$display("[%0t ns] [AXI WRITE DONE]", $time);
endtask
task axi_read(input [ADDR_WIDTH-1:0] addr, output [DATA_WIDTH-1:0] rdata);
$display("[%0t ns] [AXI READ START] Addr: 0x%h", $time, addr);
s_axi_araddr = addr;
s_axi_arvalid = 1'b1;
s_axi_rready = 1'b1;
while (!s_axi_arready) @(posedge clk);
@(posedge clk);
s_axi_arvalid = 1'b0;
while (!s_axi_rvalid) @(posedge clk);
rdata = s_axi_rdata;
// Immediate Assertion check for response code
a_read_resp_ok: assert(s_axi_rresp == 2'b00)
else $error("AXI Read response error encountered!");
@(posedge clk);
s_axi_rready = 1'b0;
$display("[%0t ns] [AXI READ DONE] Data Recieved: 0x%h", $time, rdata);
endtask
// Main Simulation Block
logic [DATA_WIDTH-1:0] read_back_data;
initial begin
clk = 0;
init_signals(); // set up initial inputs to default inactive states with 0
reset_dut(); // apply reset pulse with clock
// call axi_write task
// Test Transaction 1: Write to Address 0x100
axi_write(32'h0000_0100, 32'hAAAA_BBBB);
#(CLK_PERIOD * 3);
// call axi_read task
// Test Transaction 2: Read back from Address 0x100
axi_read(32'h0000_0100, read_back_data);
a_data_match: assert(read_back_data == 32'hAAAA_BBBB)
else $error("Data mismatch! Expected: 0xAAAA_BBBB, Got: 0x%h", read_back_data);
#(CLK_PERIOD * 3);
// call axi_write task
// Test Transaction 3: Write to Address 0x204
axi_write(32'h0000_0204, 32'h5555_4444);
#(CLK_PERIOD * 3);
// call axi_read task
// Test Transaction 4: Read back from Address 0x204
axi_read(32'h0000_0204, read_back_data);
a_data_match_2: assert(read_back_data == 32'h5555_4444)
else $error("Data mismatch! Expected: 0x5555_4444, Got: 0x%h", read_back_data);
#(CLK_PERIOD * 10);
$display("Simulation successfully completed with assertions clean.");
$finish;
end
// Waveform Generation Configuration Block
initial begin
$dumpfile("axi_lite_to_apb_dump.vcd");
$dumpvars(0, tb_axi_lite_to_apb);
end
// ========================================================================
// CONCURRENT SYSTEMVERILOG ASSERTIONS (SVA) FOR APB PROTOCOL COMPLIANCE
// ========================================================================
// quick summary of assertion principle
// assert conditions is true (or 1) else fail "error"
//
// so we can treat condition as always true
// or we can use negation to say if certain condition like RD && WR operation occurring and add negation
// to mean it should NEVER occur. !(RD&&WR) as a true NEVER occur condition.
// added comments for one example SVA below
//
// The Trigger ($rose(m_apb_pselx)): The tool looks for a rising edge on the select signal.
// It returns true if m_apb_pselx was 0 on the previous clock edge and is now 1 on the current clock edge.
//
// The Implication (|=>): This is a non-overlapping implication.
// It means: "If the trigger on the left is true,
// then the consequence on the right MUST be true exactly one clock cycle later."
//
// The Consequence (!m_apb_penable): This states that m_apb_penable must be 0 (low).
//
// Why this matters in APB Protocol
// According to the APB spec, a standard transfer has two main phases:
//
// Setup Phase: PSEL goes high, but PENABLE stays low for one cycle.
// This allows address and control lines to stabilize.
//
// Access Phase: PENABLE goes high on the next cycle to sample or drive data.
// If $rose(m_apb_pselx) happens, the bus must be in the Setup Phase on that next clock edge.
// Therefore, m_apb_penable must be low.
// If m_apb_penable is already high right after PSEL rises, the master has skipped the setup phase entirely, violating the protocol.
// 1. PSEL must go high exactly 1 clock cycle before PENABLE goes high (Setup Phase Rule)
property p_apb_setup_phase;
@(posedge clk) disable iff (!rst_n)
$rose(m_apb_pselx) |=> !m_apb_penable;
endproperty
assert property(p_apb_setup_phase) else $error("[SVA ERROR] APB Violation: PENABLE cannot go high simultaneously with PSEL.");
// 2. PENABLE must drop back down to 0 on the cycle immediately following PREADY being high
property p_apb_enable_drop;
@(posedge clk) disable iff (!rst_n)
(m_apb_pselx && m_apb_penable && m_apb_pready) |=> !m_apb_penable;
endproperty
assert property(p_apb_enable_drop) else $error("[SVA ERROR] APB Violation: PENABLE failed to drop low after PREADY transaction complete.");
// 3. APB Address (PADDR) must stay perfectly stable throughout the setup and access phase
property p_apb_addr_stable;
@(posedge clk) disable iff (!rst_n)
(m_apb_pselx && !m_apb_pready) |=> $stable(m_apb_paddr);
endproperty
assert property(p_apb_addr_stable) else $error("[SVA ERROR] APB Violation: PADDR modified mid-transaction execution.");
// 4. APB Write Data (PWDATA) must stay stable while PSEL is active during write operations
property p_apb_wdata_stable;
@(posedge clk) disable iff (!rst_n)
(m_apb_pselx && m_apb_pwrite && !m_apb_pready) |=> $stable(m_apb_pwdata);
endproperty
assert property(p_apb_wdata_stable) else $error("[SVA ERROR] APB Violation: PWDATA shifted before slave acknowledged capture.");

endmodule

//////////////// END of AXI-4 Lite to APB Converter. /////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Real Intent RDC/CDC tcl script examples

June 24, 2026

Real Intent RDC tcl script example

tcl


# ===================================================================
# 1. CLOCK DEFINITIONS
# ===================================================================
# Define functional clocks. Standard SDC style commands are supported.
create_clock -name clk_core -period 2.0 [get_ports sys_clk]
create_clock -name clk_peri -period 10.0 [get_ports p_clk]

# ===================================================================
# 2. RESET DEFINITIONS & ASYNC DOMAINS
# ===================================================================
# Explicitly tell Meridian RDC which signals act as asynchronous resets.
# This assigns them into independent reset domains.
set_reset -async [get_ports rst_core_n]
set_reset -async [get_ports rst_peripheral_n]

# Define soft/generated or internally controlled resets if needed
set_reset -async [get_pins i_power_ctrl/soft_rst_reg/Q]

# ===================================================================
# 3. MULTI-SCENARIO & MODE EXCLUSIVITY CONSTRAINTS
# ===================================================================
# Define functional scenarios where resets cannot happen simultaneously,
# allowing the tool's functional engine to filter out false paths.
set_reset_scenario -name Scenario_Normal -active {rst_core_n rst_peripheral_n}
set_reset_scenario -name Scenario_Test   -active {test_rst_n}

# Define constant or stable signals during functional modes (e.g., scan/test)
set_case_analysis 0 [get_ports scan_mode]
set_case_analysis 1 [get_ports functional_mode_en]

# ===================================================================
# 4. RDC CUSTOM CRITERIA & PARAMETERS
# ===================================================================
# Set global or path-specific non-resettable register (NRR) skip depth. 
# Tells the tool how many register stages safe crossings must utilize.
set_rdc_parameter -skip_depth 2

# Mark specific safe structures or custom synchronization nets manually 
# if they use non-standard topologies.
set_rdc_synchronizer -type double_flop -from [get_ports rst_core_n] -to [get_clocks clk_peri]

# ===================================================================
# 5. WAIVERS AND EXCEPTIONS
# ===================================================================
# Waive static/quasi-static signals that cross reset domains safely 
# (e.g., configuration registers changed only during boot up).
set_rdc_false_path -from [get_pins i_config_reg/*/Q]

Executing the RDC tcl script

mrdc -input mrdc_constraints.tcl -log mrdc.log -project meridian_project

-input (or -i): Specifies your Tcl/RDC configuration script containing your parameters.
-project: Saves the generated structural and functional database into a target directory. This allows you to open Real Intent Debug Gui directly later using idebug -project meridian_project to visualize faults and cross-reference violating logic to your RTL source code

Real Intent CDC tcl script

tcl

# ===================================================================
# 1. PRIMARY CLOCK DEFINITIONS (Standard SDC Commands)
# ===================================================================
# Define the hardware root clocks arriving from chip IO ports.
create_clock -name clk_fast -period 2.5 [get_ports sys_clk_fast]
create_clock -name clk_slow -period 12.0 [get_ports io_clk_slow]
# ===================================================================
# 2. GENERATED CLOCKS & MULTIPLEXERS
# ===================================================================
# Define clocks derived through PLLs, frequency dividers, or clock gates.
create_generated_clock -name clk_div2 \
-source [get_ports sys_clk_fast] \
-divide_by 2 \
[get_pins i_clk_gen/clk_out_reg/Q]
# ===================================================================
# 3. MERIDIAN CDC DOMAIN ASSIGNMENTS & RELATIONSHIPS
# ===================================================================
# Define clocks as completely asynchronous to instruct the structural
# analysis engine to track every transfer crossing between these boundaries.
set_clock_groups -asynchronous \
-group {clk_fast clk_div2} \
-group {clk_slow}
# Alternate Real Intent command variant to force explicit CDC verification:
# set_cdc_domain_relation -asynchronous -from clk_fast -to clk_slow
# ===================================================================
# 4. STATIC SIGNALS & CONSTANT EXCLUSIONS (Case Analysis)
# ===================================================================
# Drive static values onto mode-select pins to force the tool to
# trace only active functional paths and avoid false-positive violations.
set_case_analysis 0 [get_ports test_mode]
set_case_analysis 1 [get_ports functional_mode]
# Quasi-static signals: Signals that initialize once during boot
# and remain static can be safely declared stable to drop CDC checks.
set_cdc_stable -pins [get_pins i_system_config/reg_cfg_*/Q]
# ===================================================================
# 5. USER-DEFINED SYNCHRONIZER STRUCTURES
# ===================================================================
# Force the engine to recognize custom, non-standard synchronization structures
# such as a dedicated data-handshake bus, MUX-controlled paths, or custom FIFO.
set_cdc_synchronizer -type handshake \
-from_clock clk_fast \
-to_clock clk_slow \
-req [get_pins i_bridge/req_reg/Q] \
-ack [get_pins i_bridge/ack_reg/Q]
# Global CDC rule parameter definition
set_cdc_parameter -max_sync_flops 3 ;# Enforce 3-flop synchronizer verification

mcdc -input mcdc_constraints.tcl -log mcdc_run.log -project cdc_verification_proj

-input: Feeds the structural analysis algorithms with the clock relationships, exclusions, and parameters defined above.
-project: Consolidates design parameters and CDC errors into a unified project file database. You can directly inspect structural failures, meta-stability reports, and visual schematics by calling idebug -project cdc_verification_proj.

The complete Tcl architecture executes structural verification, detecting metastability threats, missing synchronizers, data reconvergence issues, and clock-gating structural faults across the entire design layout.

Interface Busses for DFT

June 11, 2026

IEEE 1149.1 aka JTAG

The JTAG bus has been around for many years and consists of a simple set of pins :

Input TDI, TCK, TMS, TRST

Output TDO.

Based upon the serial input bits on TDI, the JTAG FSM is stepped through the various states depending upon state of TMS = 1 or 0.

See diagram below showing JTAG physical interface, JTAG FSM and JTAG waveform example.

Controlling the FSM

To make the FSM move, you push a sequence of 0s and 1s to the TMS pin. For example:

  • Resetting: Holding the TMS pin high (1) for 5 consecutive clock cycles will always force the FSM back to the Test-Logic Reset state.
  • Shifting Data: To shift data in, you walk the FSM through the Capture, Shift, and Update states using a precise pattern of TMS toggles.

The JTAG state machine’s design is brilliant because its active shifting happens on the rising edge of the clock, while the final latching happens on the falling edge. This half-cycle delay prevents wild “thrashing” of outputs, which keeps your hardware safe while bits are flying by.

The state machine is simple, comprising two paths:

  • The data register (DR) path (shown in green), used for loading instructions
  • The instruction register (IR) path (shown in blue), used for reading/writing data from/to data registers, including the boundary scan register (BSR)

Assuming the state machine begins at Test-Logic-Reset, we begin by clocking a TMS = 0 to enter the Run-Test/Idle state, then clock a TMS = 1 to begin selecting a path.

The Core Loop: Symmetrical Paths

The FSM contains two main, symmetrical branches: one for the Instruction Register (IR) and one for the Data Register (DR). Each branch follows the exact same three steps to shift data into the chip:

Capture: The chip takes a snapshot of its current state or data and loads it into a parallel shift register.
Shift: The chip slowly passes this data out of the TDO (Test Data Out) pin while shifting new test data in from the TDI (Test Data In) pin, bit by bit.
Update: The chip latches the new shifted data onto the hardware, completing the command.

Key States Explained

Test Logic-Reset: The default starting state. All JTAG operations stop, and the chip returns to its normal, everyday function.
Run-Test/Idle: A holding state. The chip sits idle or runs a specific built-in test while waiting for the next command. Need TMS=0 to transition from Teset-Logic-Reset to Run-Test/Idle state.
Select-DR-Scan & Select-IR-Scan: The decision points. These states decide if you want to route test data (DR) or load a new command/instruction (IR). Default is Select-DR-Scan but an additional TMS=1 state with TCK will step to Select-IR-Scan state.
Capture-IR / Shift-IR / Update-IR: The states used to load a new “instruction” (like “read device ID” or “activate boundary scan”) into the chip.
Capture-DR / Shift-DR / Update-DR: The states used to send or receive the actual payload data. This will pass the JTAG shifted data register to your local register that will use it.

TAP Controller FSM with more detailed description

TAP Controller Architecture

The TAP controller manages the state machine, and depending on the state selected, the output MUX is switched.

The two paths are:

  • The instruction capture-shift path 
  • The data capture-shift path

Note how the boundary-scan register, which comprises the boundary-scan cells around the IO pins, is one of the data registers. Data registers are shift-registers, and can be of arbitrary length.

Capture, Update, and Shift States

The most ‘active’ states are the captureshift, and update states.

The capture state is perhaps the most mysterious, performing different actions for the data path compared to the instruction path. Here, capture means parallel loading data into a shift register, as opposed to shifting the data in serial into the register. Shift means, as one might expect, shifting data into the shift register. Then, the update stage latches the register, and the state machine can reset. 

Specifically, Capture-DR is the state where, if needed, test data can be parallel-loaded into the shift-capture path of the current data register. (The current data register is set by the current instruction which was previously set.) This means that data is loaded, in parallel, into the data register selected by the current instruction, as opposed to shifting in. 

Capture-IR is used for fault isolation in the JTAG system, though the standard is vague about its purpose. A fixed logic value (which must end in {…01}) is loaded in parallel into the instruction register shift-capture path. This is to say, the instruction register is parallel-loaded (instead of shifting) with a fixed logic value. 

The Shift-DR and Shift-IR states are the main states for serial-loading data into either data registers or the instruction register. While the state machine is in one of these states, TMS is held LOW, until the shifting operation is complete. The Update-DR and Update-IR states latch the data into the registers, setting the data in the instruction register as the current instruction (and in doing so, setting the current data register for the next cycle). 

IEEE 1500

The IEEE 1500 standard defines a scalable, reusable hardware wrapper and interface used to test embedded intellectual property (IP) cores within complex System-on-Chips (SoCs). It facilitates “plug-and-play” testing, allowing designers to safely isolate and test individual circuit blocks without exposing proprietary core details.

IEEE 1500 defines a standard for test access of cores within a big chip. For small designs, we just do connections at top level, and pass on signals from one module to other. But for big designs, it can get very complex. For such designs, we treat each block as a chip in itself, having it’s own controller for dft purpose, which handles all internal details of dft. Then, at top level we justhave a top level 1500 controller, which connects and controls these block level 1500 controllers. All connections are controlled via JTAG pins. These pins go into top level TAP controller, which processes and passes all the appr signals to each block.

We define standard port interface for IEEE 1500, to connect different blocks. Most signals are prefixed with W which stands for wrapper. WIR = wrapper instruction reg, while DR = data reg (WDR or wrapper data reg name not used for whatever reason). These IO signals are:

WRCK/WRCLK = Clock. This is actually connected to chip JTAG pin TCK.

WRSTN = Reset (active low). This resets all reg in WIR to 0, indicating func mod. This is actually connected to chip JTAG pin TRSTN.

SHIFTWR/SHIFTDR = Shift WIR or DR. With this signal high, Shift reg gets connected b/w WSI and WSO and starts getting values shifted in/out

UPDATEWR/UPDATEDR = Update WIR or DR. With this signal high, Update reg gets updated with values in shift reg.  These signals coming out of Update reg thenget stored in bunch of internal reg, which control all test related stuff in Func logic (i.e bist control signals, bypass, etc)

SELECTWIR = Operate on WIR. (we can have multiple SELECTWIR1, SELECTWIR2, etc if we have multiple partions within WIR, which we want to activate separately)

WSI / WSO Scan In/Out Data. These are single bit line for scanning data in and out of IR/DR. These are connected to chip JTAG pin TDI/TDO via daisy chain. First block’s WSI comes from top TAP controller, which in ultimately connected to TDI pin, WSO pin connects to next block WSI pin, and so on. Last block’s WSO pin goes to top TAP controller, which finally connects to chip TDO pin. The i/p pin WSI is captured on +ve clk edge, while o/p pin WSO is fired on -ve clk edge. This follows the same convention as for all other pins of JTAG which are fired on -ve edge, but captured on +ve edge of clk.

WPI /WPO Parallel In/Out Data. These are multi bit bus, and used for scanning data in and out of functional flops (i.e scan chain stitching of functional flops). These are same as SDI (scan Data in) and SDO (scan data out) pins that are used in designs for scan data in/out. The reason, we have a bus is to have mutiple chains of scan in/out for big designs (since they may have millions of flops, and shifting data in/out via just one pin is going to take hours). WPI/WPO are captured/fired on same clk edge as WSI/WSO.

Interface Ports and Signals

The IEEE 1500 interface standardizes access points to feed test patterns into the core. It defines two main port types, which can be used individually or combined:

Wrapper Serial Port (WSP): Mandatory pins that allow shifting data into and out of the core sequentially. Includes:

  • WSI (Wrapper Serial Input)
  • WSO (Wrapper Serial Output)
  • WRCK / WRCLK (Wrapper Clock, typically linked to the system JTAG TCK)
  • WRSTN (Wrapper Reset, active low)
  • SHIFTWR (Shift Control)

Wrapper Parallel Port (WPP): Optional, scalable ports that allow massive parallel data transmission to meet high-bandwidth test requirements.

Core Interface Architecture

The standard separates internal core testing from chip-level operations by enveloping the core in a specific wrapper interface.

The IEEE 1500 interface consists of two primary hardware components:

  • Wrapper Boundary Register (WBR): Composed of wrapper cells on each functional input and output pin. It acts as an isolation barrier, allowing the application of test vectors and serialization/deserialization of data between the core under test (CUT) and surrounding logic.
  • Wrapper Instruction Register (WIR): Configures the wrapper into various test modes and initiates all test activities (e.g., INTEST for internal core testing, EXTEST for external interconnect testing).
  • Wrapper Bypass Register (WBY): Provides a short path through a wrapped core so test data can reach other modules without unnecessary delays.

IEEE 1500 Core Wrapper Architecture (Top Left): This section shows how an embedded core is isolated. Key components like the Wrapper Instruction Register (WIR), Wrapper Data Registers (WDR), and the vital Wrapper Bypass Register (WBY) and Wrapper Boundary Register (WBR) are highlighted. These registers are essential for shifting test data into the core while bypassing others.

IEEE 1500 Standard Interface Signals (Top Right): This provides a breakdown of the required signals, including those for serial data transfer (WSI/WSO) and parallel data transfer (WPI/WPO). The table defines the standard abbreviations used in the diagram (e.g., WRCK for Wrapper Clock).

Sample Protocol Waveform: INTEST and EXTEST (Bottom): This timing diagram shows a sequential test.

  • First, the INTEST (Internal Test) instruction is loaded, configuring the wrapper boundary to test the core logic internally.
  • Next, the EXTEST (External Test) instruction is loaded, which is often used for interconnect testing between cores.
  • The diagram illustrates the corresponding states of the Wrapper Clock, the Wrapper Shift signal, and the Wrapper Data signals, showing how the Wrapper Boundary Register is loaded with test patterns and then captured and shifted out.

IEEE P1687

IEEE 1687, commonly known as IJTAG (Internal JTAG), is an IEEE standard that provides a uniform, automated way to access and control embedded instruments within a semiconductor device. It creates a bridge between standard external chip pins and internal testing features.

Why IJTAG is Needed

Modern chips are incredibly complex and contain thousands of tiny monitoring tools (called “instruments”) used for testing, debugging, and post-silicon validation. Traditional methods required custom wiring for every tool, which was highly inefficient. IJTAG standardizes the architecture so these instruments can be easily accessed without changing the core design of the chip.

The Two Core Languages

IJTAG achieves its flexibility by separating the hardware design from the software commands. It uses two specialized languages:

ICL (Instrument Connectivity Language): Describes the hardware structure of the on-chip network. It details how data flows through the chip and which pathways lead to which instruments.
PDL (Procedure Description Language): Describes the software instructions used to test or operate the instruments. PDL tells the system what data to send and what results to expect, without worrying about the physical path.

The Translation (Retargeting) Process

One of the biggest advantages of IJTAG is the retargeting engine. A test engineer can write a PDL script for a single, specific instrument inside the chip. The IJTAG software automatically takes that simple script and translates it into chip-level scan vectors. This means the software calculates exactly what needs to be fed into the main pins of the chip to reach the target instrument.

Key Hardware Components

  • Gateway: The entry point where test data enters the on-chip IJTAG network.
  • SIB (Segment Insertion Bit): A SIB acts like a digital switch. It allows the testing system to dynamically turn on or bypass specific branches of the instrument network. This shortens the data path, saving time and memory during testing.

Constraints for CDC with DFT

June 9, 2026

CDC Background

Design for Test (DFT) insertion alters your chip’s circuitry by adding test modes, scan chains, and clock multiplexers. Without the proper DFT constraints, Static Timing Analysis (STA) and Clock Domain Crossing (CDC) tools will generate thousands of fake errors. You must constrain these test signals properly to isolate functional clock crossings.

1. Fix the Scan Enable (SE) Signal

Your Scan Enable (SE) signal tells the chip whether it is operating normally or in test mode. During functional analysis, you must tell the tool that SE is completely inactive so it does not evaluate impossible test paths. [1]

Apply this Synopsys Design Constraint (SDC) or Xilinx Design Constraint (XDC) early in your flow:
set_case_analysis 0 [get_ports scan_enable]

2. Isolate Test Clocks

DFT often forces fast clocks to interact during testing. If these paths are reported by your CDC tool as un-synchronized, your tool will flag endless violations. You must treat these as asynchronous clock domains.

Instruct the tool to ignore timing on test crossings by specifying the domain:
set_clock_groups -asynchronous -group [get_clocks test_clock] -group [get_clocks functional_clock]

3. Constrain Datapaths

For valid CDC paths that exist to test asynchronous logic, you must limit the delay so that tools can successfully sample the data. Constrain the maximum data path delay to avoid violating setup and hold times.

Use the data path constraint command:
set_max_delay -datapath_only -from [get_clocks source_clock] -to [get_clocks dest_clock] <delay_value>

4. Separate DFT Domains

A best practice during DFT scan insertion is to group scan cells from different clock domains into separate scan chains. This prevents metastability from destroying your test data. If you use tools like Tessent Scan, you can add this option to ensure all scan chains belong to the same clock domain:


add_scan_mode -single_clock_domain_chains

Why This Matters

If you do not set these constraints, testing logic will look like illegal RTL clock domain crossings. By defining case analysis and grouping your clock domains properly, you allow your EDA tools to focus only on real functional bugs.

CDC Constraints

About CDC Constraints

Clock Domain Crossing (CDC) constraints apply to timing paths that have a different launch and capture clock. There are synchronous CDC and asynchronous CDC depending on the launch and capture clocks relationship and on the timing exceptions set on the CDC paths. For example, CDC paths between synchronous clocks but covered by false path constraints are not timed, and consequently are treated as asynchronous CDCs.

Asynchronous CDC paths can be safe or unsafe. The terminology of safe and unsafe for asynchronous CDC paths is different from the terminology used for inter-clock timing analysis (see report_clock_interaction). An asynchronous CDC path is considered safe when it uses a synchronization circuitry to prevent metastability of the capture sequential cell.

The timing analysis of CDC paths can be fully ignored by using 

set_false_path or set_clock_groups constraints,

or partially analyzed by using 

set_max_delay -datapath_only.

In addition, the multibit CDC paths capture time spread can be constrained using the 

set_bus_skew constraint.

Constraining Bus Skew

About Bus Skew Constraints

The bus skew constraint is used to set a maximum skew requirement between several asynchronous CDC paths. The bus skew is not the traditional clock skew associated with a timing path. Instead, it corresponds to the largest capture time difference across all the paths that are covered by a same set_bus_skew constraint. The bus skew requirement applies to both Fast and Slow corners, but it is not analyzed across the corners.

The intent of the bus skew constraint is to limit the number of source clock edges that can launch a data and be captured by a single destination clock edge. The tolerance depends on the CDC synchronization scheme used for the constrained paths. The bus skew constraint is typically used for the following CDC topologies:

  • Gray-coded bus transfer, such as in asynchronous FIFOs
  • Multi-bit CDC implemented with CE, MUX, or MUX Hold circuitry
  • Configuration registers

Although the set_bus_skew command does not prevent a bus skew constraint to be set on a safely timed synchronous CDC, such a constraint is not needed. The setup and hold checks already ensure a safe transfer between two safely timed synchronous CDC paths.

The CDC scenarios for bus skew constraints are:

  • Asynchronous CDC covered with set_clock_groups
  • Asynchronous CDC entirely covered with set_false_path and/or set_max_delay -datapath_only
  • Synchronous CDC paths covered with set_false_path and/or set_max_delay -datapath_only

The bus skew constraint is not a timing exception; rather, it is a timing assertion. Therefore, it does not interfere with the timing exceptions (set_clock_groupset_false_pathset_max_delayset_max_delay -datapath_only, and set_multicycle_path) and their precedence.

Why CDCs Break During DFT

  • Shift vs. Capture: In normal operation, circuits use synchronizers (like FIFOs or multi-flop chains) to safely pass data between clocks. During DFT, the scan path bypasses this logic, linking flip-flops in a raw chain. This exposes paths that are unsafe for test data.
  • Clock Skew: Scan data shifts using different clock phases. This can cause signals to arrive too late at the receiving flip-flop, resulting in data loss.

How to Fix Scan CDC Issues

  • Lock-Up Latches: DFT tools insert “lock-up latches” between scan flip-flops that sit on different clock domains. These latches act as a temporary buffer. They hold the data stable until the next clock cycle begins, preventing data loss.

Diagram Explanation

  1. Clock Domains: The diagram is split vertically into two distinct domains: a faster Clock Domain A (Source) and a slightly slower Clock Domain B (Destination).
  2. Potential Problem: During a scan-shift operation, data must travel from the final scan flip-flop (CFF1) in Domain A to the first scan flip-flop (CFF2) in Domain B. If CFF2 captures the data too quickly after it is launched by CFF1, a hold-time violation can occur, resulting in invalid test data.
  3. The Solution (Lockup Latch): By placing a lockup latch between the domains and triggering it on the opposite clock edge of the preceding flip-flop, we insert a mandatory half-clock cycle of delay (Hold Padding). This added delay ensures the data remains stable before CFF2 samples it, guaranteeing reliable cross-domain communication during testing.

  • Test Clocks: Testers often group or slow down clocks globally during test modes. This allows the chip to capture data at a relaxed speed without timing mismatches.

Verification and Sign-Off

Because DFT insertion alters the netlist, standard RTL (Register-Transfer Level) CDC checks are not enough. Engineers perform dedicated CDC sign-offs on the DFT netlist.

  • Identify Violations: Use tools like Real Intent or VLSI Guru to spot where un-synchronized scan paths cross clock domains.
  • Isolate Domains: Use DFT-specific clock controllers to mask paths or bypass asynchronous crossings so that Automatic Test Pattern Generation (ATPG) works properly.

To check for Clock Domain Crossing (CDC) issues using Synopsys SpyGlass, you will primarily use the SpyGlass CDC product (built on the underlying GuideWare methodology). The process involves setting up your design, specifying constraints (clocks, resets, and domain definitions), and running the CDC goals.

SPYGLASS FOR CDC CHECKS

Here is a comprehensive breakdown of the essential SpyGlass commands and a standard Tcl script structure used to run a CDC analysis.

1. Core SpyGlass CDC Commands

These are the primary Tcl commands used inside a SpyGlass project file (.prj) or interactive shell.

Project Setup Commands

  • new_project <project_name> -over – Creates a new SpyGlass project directory.
  • set_option top <top_module_name> – Specifies the top-level design module.
  • read_file -type verilog <file_name>.v – Reads RTL design files (supports -type vhdl and -type sverilog).
  • read_file -type sgdc <file_name>.sgdcCritical for CDC. Reads the SpyGlass Design Constraints file where clocks, resets, and abstract rules are defined.

Goal Management Commands

  • current_goal <goal_name> -top <top_module> – Selects the specific CDC analysis rule-set to execute.
  • run_current_goal – Executes the selected goal on the design.

2. Standard CDC Goals Sequence

SpyGlass CDC checks are performed in a sequential, iterative pipeline. You start with basic setup checks and move toward complex structural and functional verification.

Goal NamePurposeWhat it Checks
cdc/cdc_setupSetup ValidationValidates your SGDC file definitions. Checks if all clocks/resets are defined and correctly propagated.
cdc/cdc_setup_checkEnhanced SetupIdentifies unconstrained clocks, black-boxes, or incorrectly defined constants.
cdc/clock_reset_integrityClock/Reset QualityChecks for glitches, bad muxing, or deep combinational logic in clock/reset paths.
cdc/cdc_verify_structStructural VerificationThe core CDC check. Finds missing synchronizers, data holding violations, and structural crossings.
cdc/cdc_verifyFunctional VerificationRuns formal/functional checks on handshake protocols and FIFO depth/stability.

Template SpyGlass CDC Run Script (run_cdc.tcl)

You can aggregate these commands into a single Tcl script to automate your analysis:

Tcl

# ----------------------------------------
# 1. Project Initialization
# ----------------------------------------
new_project my_cdc_project -over
set_option top my_top_module
# ----------------------------------------
# 2. Read Design Files & Constraints
# ----------------------------------------
# Read RTL
read_file -type sverilog ./rtl/fifo.v
read_file -type sverilog ./rtl/my_top_module.v
# Read CDC Constraints (Mandatory)
read_file -type sgdc ./constraints/cdc_constraints.sgdc
# ----------------------------------------
# 3. Configure SpyGlass Options
# ----------------------------------------
set_option language_mode mixed
set_option active_methodology $SPYGLASS_HOME/GuideWare/latest/block/rtl_handoff
# ----------------------------------------
# 4. Run CDC Methodology Sequence
# ----------------------------------------
# Step 1: Validate setup and clock structures
current_goal cdc/cdc_setup -top my_top_module
run_current_goal
current_goal cdc/clock_reset_integrity -top my_top_module
run_current_goal
# Step 2: Perform structural CDC analysis (most violations are caught here)
current_goal cdc/cdc_verify_struct -top my_top_module
run_current_goal
# Step 3: Run comprehensive functional CDC checks (Optional/Advanced)
current_goal cdc/cdc_verify -top my_top_module
run_current_goal
# ----------------------------------------
# 5. Save and Close
# ----------------------------------------
save_project

4. The SGDC File (Why Commands Aren’t Enough)

SpyGlass cannot accurately identify CDC violations without a SpyGlass Design Constraints (.sgdc) file. It tells the tool which signals belong to which clock domains.

Below are the critical commands used inside the .sgdc file:

Tcl

# Define Clocks and assign them to distinct domains
current_design "my_top_module"
clock -name "clk_core" -domain DOMAIN_A -tag DOMAIN_A
clock -name "clk_pci" -domain DOMAIN_B -tag DOMAIN_B
# Define Resets
reset -name "rst_n" -value 0 -sync
# Define Quasi-static signals (signals that change rarely, ignoring CDC checks on them)
abstract_port -ports "cfg_mode" -value stable
# Group synchronous clocks together (if they originate from the same PLL and are synchronous)
clock_egress -clocks clk_div2 clk_div4 -source clk_main

5. How to View Results

After executing your script, you can view the results via command-line reports or the GUI:

  • Via GUI (Recommended for CDC debugging):Bashspyglass -project my_cdc_project.prj & Once the GUI opens, navigate to the Incremental Schematic window to trace the exact path of a unsynchronized violation (e.g., source flip-flop $\rightarrow$ combinational logic $\rightarrow$ destination flip-flop).
  • Via Text Reports: Check the my_cdc_project/my_top_module/<goal_name>/spyglass_reports/ directory. Look specifically for moresimple.rpt for a clean summary of the warnings and errors.

Understanding CDC / RDC and How to Fix Issues

June 8, 2026

We live in an asynchronous world where events happen all the time without us knowing it. However, we also live in a clocked world where we need to know the absolute time so that we can properly handle these asynchronous events. The role of CDC (clock domain crossing) / RDC (reset domain crossing) is to handle these asynchronous events externally to a semiconductor chip or SOC as well as internally where there will be multiple clock domains with signals crossing back and forth between these clocked interfaces. Some CDC/RDC principles are gleamed from my years of work experience and some are learned from Ganga Nunna on LinkedIn. I have categorized into my own general topics based upon my experience.

What is CDC / RDC ?

What is CDC ?

CDC is the process of transferring signals and/or data between two logic circuits operating in different clock domains. (e.g. one could be 1 MHz frequency and another could be 1000 MHz).
It enables safe communication between blocks that do not share the same clock, frequency, or phase. So handling signal and/or data crossings from slow to fast domains need to handled safely to ensure proper operation of the logic circuits.

Why do we need CDC ?

Modern SoCs like AI Compute chips or Servers or Space target chips run on multiple clocks for performance, power, architecture, and functional reasons. One always desires maximum performance with minimal power and minimal cost.
When a signal crosses domains without proper signal handling, this may cause various issues in the SoC such as :
• Metastability – signals fluctuate between logical high and logical low over a period of time. This can lead to data corruption.
• Data corruption – instead of an expected signal being logically high its actually logically low
• Data loss / incoherence – because certain signals are logically low instead of high data transfers do not occur and so data loss.
• Functional failures – on a larger picture once various signals incorrectly stay logically high or low, functional failures can occur.
Therefore, CDC techniques are essential to ensure reliable SoC chip operation.

When does CDC occur ?

CDC occurs whenever:
• Two modules operate on different clocks such as 1 MHz versus 1000 MHz
• Clock frequencies differ (e.g. 1 MHz versus 1000 MHz)
• Clocks come from separate PLLs / oscillators either internally or externally to the SoC.
• The phase relationship between clocks is unknown or asynchronous. (This is important as one can have the same clock frequency but operate in a different phase).

Where is CDC applied in the SoC ?

CDC is utilized in many SoC designs due to the large usage of multi-clock domains of multiple IPs (e.g. PLL, HBM,CPU, Ethernet, USB Serializers like a keyboard):
• Multi-CPU cores and multi-clock IPs (PLL, HBM, DRAM, Ethernet, USB)
• APB <-> AXI <-> AHB <-> CHI <-> Customized bridges
• FIFO-based data movers. (FIFOs are needed when write bursts are faster than the read bursts and need to use FIFO as temp local data storage)
• Slow–fast domain handshakes such as interrupts
• Interrupts and resets (important signals that need to be properly handled)
• DMA, sensor interfaces, network-on-chip paths between 2 different clock domains.
Anywhere 2 or more clocks differ in frequency or phase → CDC is required.

What is metastability and why I should care about it ?

What is metastability ?

Metastability is when a signal coming out of a flip-flop is in indeterminate state : neither a logical one nor a logical zero. This block digram shows what occurs from the first flip-flop which feeds into a second flop which form a 2-flip-flop synchronizer pair. Metastability occurs when the setup or hold required time for the first flip-flop is violated. The second flip-flop is needed to resolve the first flip-flop issue.

A flip-flop needs stable data (logic levels for high and low) around the clock edge to satisfy its timing requirement for :
• Setup time
• Hold time

If data changes too close to that edge, the flip-flop cannot decide between 0 or 1 → it enters an unstable and unknown analog state as seen below:

This unstable temporary condition is called metastability.

What’s the internal semiconductor device behavior ?

Internally a flip-flop has:
• Two cross-coupled inverters
• Forming a positive feedback loop

If input changes during sampling:
• Both inverters see conflicting voltages
• Internal nodes fight
• Output becomes unstable → metastable

It’s like balancing a pencil on its tip — not stable.

What’s the output behavior look like ?

• Output rises or falls very slowly
• Stays in-between 0 and 1
• Suddenly settles to a random final value

Why should I care about metastability ?

One needs to pay close attention to metastability because having indeterminate logical signals : 1 or 0, can cause the logic circuit to behave erratically eventually causing functional failure which will cause SoC or system failure. In the digital domain point of view all signals must be either a logic 1 or a logic 0 and properly controlled.

It can cause:
• Random output value
• Extra propagation delay
• Glitches
• Wrong control decisions
• Corrupted state machines
• Multi-bit mismatch
• Lost or duplicated events

These failures can break entire chip subsystems and eventually the entire system.

What is MTBF (Mean Time Between Failure)

Metastability cannot be completely eliminated or removed, but its probability can be reduced.

MTBF = Expected average time between metastability-induced failures.

In general, to increase MTBF:
• Use 2-FF or 3-FF synchronizers
• Give more time for settling
• Reduce the toggle rate of async signals
• Use robust flip-flops (some fabs create standard cells libraries with hardened or specially designed flip-flops just for reduced MTBF)

Good CDC design can push MTBF to millions of years —
meaning practically zero failure in product lifetime.

What logic / RTL design principles to apply for CDC :

We reduce probability of MTBF occuring by using these techniques:
• 2-FF synchronizer
• 3-FF synchronizer
• Handshake protocols
• Async FIFOs
• Gray coding
• CDC constraints

Ultimate Goal is to give the metastable output enough time to settle before use which can mean slower clock but this defeats the purpose of operating at higher frequencies. So best logical effort is to apply the techniques above.

What type of failures occur in the digital design when CDC / RDC is violated ?

It can cause at a low level:
• Random output value
• Extra propagation delay
• Glitches
• Wrong control decisions
• Corrupted state machines
• Multi-bit mismatch
• Lost or duplicated events

At higher level these issue may appear:

• Random modes of system operation
• Frozen operation and will need hard reboot (push the power off and on)
• Wrong and duplicate data transfers

Why use CDC/RDC tool and not STA to help catch CDC / RDC issues ?

Role of STA in SoC design

Static timing analysis (STA) is mainly used to time set up and time hold times in a logic circuit and not catch cross domain crossing issues.

STA works perfectly only when launch and capture flip-flops use the SAME clock domain, meaning one fully knows:

• Clock arrival time
• Clock delays
• Clock phase difference
• Data arrival time and data path delay

STA assumes a known, predictable relationship between the two clocks

But modern SoCs contain:

• Multiple PLLs from general clocks to high-speed serializers (e.g. UCIe, USB, PCIE, Ethernet, AXI, AXI, APB)
• Different clock frequencies (e.g. 1 MHz versus 1000 MHz)
• Unknown clock phases
• Clock dividers / clock muxes (one may need to use clock divider to slow a clock so complex logic like multipliers can be performed in the clock period)
• Multiple asynchronous subsystems (e.g. UCIe, USB, PCIE, Ethernet, AXI, AXI, APB)


• Known frequency + known phase → ✔ STA works
• Known frequency + unknown phase → ✖ STA fails (✔ use CDC)
• Unknown frequency and phase → ✖ STA cannot be used (✔use CDC)

Whenever the phase is unknown, STA cannot determine setup/hold timing. These paths will be treated as “unconstrained” and cannot be measured using STA.
So it cannot guarantee that data will not change near the sampling edge.

What checks are done by STA ?

STA detects only timing-based failures such as:

• Setup time → data must arrive early enough
• Hold time → data must remain stable long enough
• Delay, skew, jitter → path timing must meet constraints

• clock latency → clock buffers are formed serially so adding up these buffers becomes the clock latency

• Setup Timing Analysis → data arrives before the required window
• Hold Timing Analysis → data must not change near the clock edge

STA assumes the timing can be controlled and predicted —
But this is NOT true when clocks are asynchronous or unrelated.

The paths that STA cannot control or predict are treated as “unconstrained” and these must be carefully analyzed and constrained. Usually paths to the first flip-flop stage are treated as false paths with “set_false_path -to DFF1/D” so that any long paths are not timing analyzed as they may trigger a false positive setup timing violation. So use CDC to analyze this path.

So STA cannot protect us from CDC failures.

Why use CDC analysis instead of STA analysis ?

When two flip-flops run on different clocks, the data changes at:

• Unknown times
• Unpredictable phases
• Random boundaries

This virtually guarantees repeated setup and hold violations at the domain boundary. This will cause metastability which is illustrated in the previous metastability diagram.

STA cannot calculate timing when timing itself is unpredictable.

So as the acronym implies : CDC , it is a cross clock (cross phase) domain crossing check at interfaces within a SoC.

What are the failures seen for CDC ?

Data Corruption

This occurs when the destination flip-flop samples metastable data.

Can result in:
• Wrong logic values
• Unpredictable behavior
• FSM jumping to invalid states
• Interrupt mis-triggering

Root cause:
Async data toggles near the capture clock edge which leads to setup/hold violation. This can be seen as incorrect logic values.

Impact:
Control logic becomes unstable and not controllable. Some FSM transitions can occur without the correct input conditions.

Data Incoherence

Multi-bit buses cross domains without proper synchronization.

Example:
Source changes 0101 → 1010
Receiver may capture:
• 0001
• 0110
• 1011
• Or any random mix from 0000 up to 1111 depending upon the physical semiconductor behavior

Root cause:
Each bit meets timing differently and may be captured in different cycles.

Impact:
This results in incorrect addresses, data transfer, counter values, and status signals.

Data Loss

If a pulse/event is shorter than one destination clock period,
the receiving domain may completely miss it. Basically, the signal is never captured by destination or receiver clock.

Example:
Source = 1000 MHz
Destination = 1 MHz

The 1 nanosecond pulse is too short to be captured by the slower 1000 nansecond clock period. A 1-cycle source pulse will disappear.

Root cause:
Destination clock is too slow to sample the short lived and fast event.

Impact:
Missed interrupts, missed handshakes, inconsistent behavior.

Data Duplication

Destination domain sometimes sees the same data event twice.

Root cause:
Metastability delay → FF2 samples an incorrect transition twice.

Impact:
Counters increment twice, FSM moves twice, protocols break.

Chip Burning (Short-Circuit Current)

A lesser-known but real hardware risk.
When a node becomes metastable, both PMOS & NMOS inside an inverter can partially turn ON at the same time as seen in diagram below :

causing:

Direct current from VDD → GND
➡ Heat generation
➡ Hotspots
➡ Long-term reliability issues (high current and heat always causes breakdown of semiconductor material)

Impact:
Block-level and/or even chip-level damage and/or system damage if not handled correctly.

Mapping these SoC problems to CDC fixes

Data Corruption – 2-FF / 3-FF Synchronizers
Data Incoherence – Gray Code, Multi-bit Sync
Data Loss – Toggle Sync, Pulse Stretching, Handshake Data Duplication – Handshake, Toggle Sync
Chip Burning – Correct Synchronization (hardened or robust synchronizers to handle MTBF better)

CDC failures include data corruption, incoherence, loss, duplication,
and even hardware stress.
Every CDC technique (2-FF sync, toggle, handshake, FIFO) exists
to eliminate one or more of these issues.

How to fix CDC ?

We reduce probability of MTBF occuring by using these techniques:
• 2-FF synchronizer
• 3-FF synchronizer
• Handshake protocols
• Async FIFOs
• Gray coding
• CDC constraints

2 stage FF synchronizer (fix data corruption)

What Problems It Solves & Why It Works
The 2-FF Synchronizer seen below is the most widely used CDC structure in every SoC.
It solves one critical problem extremely well: Data Corruption caused by metastability.

What Problem Does 2-FF Sync Solve?

2-FF synchronizer solves ONLY “Data Corruption”

When Do We Use a 2-FF Synchronizer?


Use it for single-bit control signals, such as:
• enable
• valid
• ready
• interrupt
• status flags
• mode change
• reset release (carefully use and keep 2FF together to ensure there’s no setup/hold violation to second FF)
❌ NOT for multi-bit data
❌ NOT for pulses
❌ NOT for counters
❌ NOT for buses

How 2-FF Synchronizer Works 

The FF_src outputs a signal in source clock domain which crosses the CDC boundary to FF_sync1 which is destination clock domain. Note that source clock domain and destination clock domain operate independently and is asynchronous to each other. They could be coming from different PLLs on the SoC chip or from offchip PLL.

Async Input from FF_src
|
[FF_sync1] ← may go metastable
|
[FF_sync2] ← stable output (SIG_sync)
|
Synchronized Signal
FF_sync1:
• Samples async input
• May violate setup/hold
• May go metastable
FF_sync2:
• Samples FF_sync1 after one full clock period
• Gets enough time for FF_sync1 to settle
• Produces a clean and stable output : SIG_sync
====>. Metastability stays inside FF_sync1 and never reaches your logic after SIG_sync.

Why does more time improve MTBF (Mean Time Between Failures) ?

Metastability reliability is measured using MTBF (Mean Time Between Failure).
Conceptually:
MTBF ∝ e^(Ts / τ)
Where:
• Ts = settling time
• τ = technology constant
Adding FF2 increases “Ts” by one full clock cycle → failure probability drops exponentially.
That’s why:
1 FF → unsafe
2 FF → safe for almost all control signals
3 FF → used for automotive, aerospace, medical (tradeoff is slightly more latency for increased reliability)

What does 2FF synchronizer Solve vs. What does it not solve ?

Does Solve:

• Data Corruption → output becomes stable

Does NOT solve:
• Data Incoherence (multi-bit)
• Data Loss (short pulses)
• Data Duplication
• Chip Burning (FF_sync1 still metastable inside)

Where 2-FF Sync Is Used in Real SoC Chips

• Interrupts (IRQ)
• Status flags (FIFO Full, FIFO Empty, Cache Hit/Miss, etc)
• Mode change bits (GPU_Enable, Cache_Enable, etc)
• Power-domain handshake bits (req, ack)
• Clock gating enables (clock_en)
• GPIO / external inputs (GPIO_input[7:0])
Every SoC uses thousands of these.

Must-Follow Design Rules

✔ No combinational logic between FF_sync1 and FF_sync2
✔ Both FFs (FF_sync1, FF_sync2) must use destination clock
✔ Use hardened “synchronizer flops” from fab library if available
✔ Place FF_sync1 & FF_sync2 physically close on the die (to avoid setup/hold (clk skew) issues between the flops)
✔ Add a 3rd FF for high-safety designs (will add one clock latency but increases MTBF)

Pulse Synchronizers — How to Prevent Data Loss in CDC

Previous section we saw that 2-FF synchronizers fix data corruption for level signals (signals that generally stay either high or low most of the time).
But for pulse signals, 2-FF is NOT enough. We need a pulse synchronizer. The diagram below shows the 2FF synchronizer on the left and the one shot pulse generator on the right which uses back to back flip-flops with XOR gate to generate a one shot pulse.

What Is a Pulse in CDC?

A pulse is a signal that:
• Goes HIGH for 1 cycle or more cycles
• Then returns LOW

Examples:
• Interrupt
• Event completion
• FIFO full
• Timer expiration
• DMA request (immediate req)
• Single-cycle enables
===>. These signals represent events, not levels — and events must never be missed.

Why Can’t 2-FF Synchronizer Transfer Pulses Safely?

Because pulses can get:
Lost – if the pulse is too short and not captured by destination clock
Duplicated – if FF_sync1 metastability causes re-sampling
Stretched/Shrunk – depending on clock edges (pulse can get reduced depending upon clock edge sampling)
So we need something better than 2-FF.

Pulse Loss Example

Source clock: 1000 MHz
Destination clock: 100 MHz
A 1-cycle pulse in 1000 MHz = 1 ns
Destination samples every 10 ns
===> The pulse may occur entirely between two destination edges which means the pulse is lost.

Pulse Duplication Example

If FF_sync1 becomes metastable:
• FF_sync2 may sample the transition twice
===> One pulse becomes two pulses
This is a general CDC failure.

The Solution: Pulse Synchronizers

There are three main implementation styles:

🟧 Style 1: Toggle-Based Pulse Synchronizer (as seen above) (MOST RELIABLE)
Step 1 → Convert pulse to toggle
Step 2 → Synchronize toggle using 2-FF
Step 3 → Convert toggle change back to pulse
✔ No loss
✔ No duplication
✔ Works for all fast-to-slow CDC crossings
✔ Most widely used in industry


🟧 Style 2: Pulse Stretching
Increase pulse width using OR gate and extra flip-flops (e.g., 1 cycle → 3–4 cycles).
Useful only when destination clock is known to be faster.

Note : Not general-purpose and very specific usage.

🟧 Style 3: Handshake Pulse Synchronizer
Source waits for ack before sending next pulse.
✔ No loss
✔ No duplication
✔ Guaranteed delivery
✔ Best for high-reliability SoCs


Which Method Should You Use?


✔ Toggle Synchronizer → Best general solution
✔ Handshake → Best safety & reliability
✔ Pulse Stretching → Clock-dependent
✘ 2-FF → Never for pulses

Which CDC Concerns Does Pulse Synchronizer Solve?


• Data Corruption → ✔ Yes
• Data Loss → ✔ Yes
• Data Duplication → ✔ Yes
• Data Incoherence → ✘ Not related
• Chip Burning → ✔ Avoids metastable retrigger

Pulse Synchronization Summary


Pulse synchronizers prevent data loss and duplication when transferring short events across clock domains. Toggle-based and handshake pulse synchronizers are the most reliable and widely used solutions in industry.

Toggle Synchronizer – Safest Way to Transfer Events Across Clocks

The toggle synchronizer is one of the most reliable CDC techniques for transferring pulses/events across asynchronous clock domains. It creates a toggle state in source clock domain and utilizes the pulse synchronization on destination clock domain. See block diagram below generated by Gemini AI tool.


It solves major CDC issues like:
• Data loss
• Data duplication
• Missed pulses
• Pulse shrinking/stretching
• Metastability propagation

Why Does One Need a Toggle Synchronizer?

Pulse signals face serious CDC issues:

• Pulses can be missed in fast→slow transfers
• One event can appear twice
• Pulse width is not preserved
• 2-FF synchronizer alone cannot safely transfer pulses
To fix this, we convert the pulse into something stable:
Convert pulse → Toggle → Synchronize → Convert back to pulse

How Toggle Synchronizer Works ?

Step 1: The pulse toggles a flip-flop in the source domain
→ toggle <= ~toggle
Step 2: This toggle bit is synchronized using a 2-FF synchronizer
→ treat the async toggle bit like a level signal
Step 3: Destination detects a change in toggle
→ change = sync_toggle XOR prev_sync_toggle
→ Generate a 1-cycle clean pulse in destination domain

Why Toggle Synchronizer Is Very Reliable ?


✔ No pulse loss — event stored as a toggle
✔ No duplication — one toggle = one event
✔ No metastability propagation — isolated to FF1
✔ No pulse width issues — toggle is a stable level
✔ Works for fast→slow clocks without missing events

When To Use a Toggle Synchronizer ?

Use it for one-shot events such as:
• Interrupt pulses
• DMA done / start
• FIFO threshold events
• Timer expiration
• Control event triggers
• Sensor events (temp exceeded threshold like 125C)
• CPU to slow peripheral communication like USB (mouse or keyboard)
Common used in SoCs with peripherals, pipelines, automotive FuSa logic.

What Problems Does Toggle Sync Solve?

CDC Issues Solved

  • Data Corruption✔ Yes
  • Data Loss✔ Yes
  • Data Duplication✔ Yes
  • Pulse Width Issues✔ Yes
  • Data Incoherence✘ (multi-bit issue)

Toggle Synchronizer Limitations

❌ Cannot handle back-to-back pulses (events may merge)
❌ Works only for single-bit events
❌ Destination must detect toggle change before next event

For 100% reliable event transfer use Handshake Synchronizer (see next section)

A toggle synchronizer converts a short pulse into a stable toggle, synchronizes it safely with a 2-FF chain, and recreates a clean pulse in the destination domain.
This avoids data loss, duplication, and metastability issues during pulse transfer.’

Handshake Synchronizer — Guaranteed No Loss, No Duplication

From previous sections, we know 2FF synchronizer prevents data corruption for level and single bits

• 2-FF synchronizer → prevents data corruption
• Toggle synchronizer → prevents data loss & duplication but fails for back-to-back pulses

Now we discuss the Handshake Synchronizer — the most reliable CDC method for control/event transfer.
It guarantees:
✔ No event lost
✔ No event duplicated
✔ No overwrite
✔ Works for both directions of a fast↔slow cross clock domains
✔ Fully safe for control signals

Why Do We Need a Handshake Synchronizer?

Toggle synchronizer fails when:
• Two pulses come too fast (basically back to back pulses)
• New pulse arrives before destination captures previous toggle
• Continuous events must be reliably transferred
Handshake fixes this by enforcing:
– Source cannot send the next event until destination acknowledges the previous one.
This makes it 100% reliable. It does have a drawback of increased latency (time delays for secure handshake)

❌ ADD DIAGRAM SHOWING HANDSHAKE SYNCHRONIZATION CONNECTIONS

Basic Principle of Handshake (REQ–ACK Protocol)

Two control signal are required for this to work:
• REQ — Request from source clock domain to destination clock domain.
• ACK — Acknowledge from destination clock domain back to source clock domain.

Sequence of operations:
1️⃣ Source sets REQ = 1 (event occurred in source clock domain)
2️⃣ Destination receives REQ
3️⃣ Destination sets ACK = 1 (to acknowledge receiving the REQ)
4️⃣ Source sees ACK and clears REQ = 0
5️⃣ Destination sees REQ cleared → clears ACK
6️⃣ System ready for next event
✔ Closed-loop
✔ No loss
✔ No duplication
Both REQ and ACK cross using 2-flop synchronizers.

How it Works (Step-by-Step) ?

Step 1 — Pulse comes → Source sets REQ = 1
Step 2 — REQ crosses domain via 2-FF
Step 3 — Destination sees REQ = 1 → generates event pulse → sets ACK = 1
Step 4 — ACK crosses back to REQ clock domain
Step 5 — Source sees ACK → clears REQ
Step 6 — Destination sees REQ=0 → clears ACK
✔ No event lost
✔ No event duplicated
✔ No metastable propagation
✔ Next event allowed only after ACK.

❌ Drawback is cannot have a short time period to process next REQ. There is overhead latency to do synchronization and then resetting REQ.

Why Handshake Synchronization Is Guaranteed Safe ?


• REQ holds event until destination confirms with ACK sent back to source clock domain
• ACK ensures destination actually processed event
• 2-FF sync protects both directions
• No overwriting
• Exactly one pulse generated
• Works for both fast→slow or slow→fast due to handshake.
This is the strongest CDC method for single-bit event transfer.

Where Used in Real SoCs ?

Used in:
• DMA start/stop
• CPU ↔ Peripheral signalling
• APB–AXI bridges with different clock domains
• Sensor events (e.g. temp sensor reached 125 C)
• Reset release
• Power-mode control
• Slow→fast state transfers
• Functional Safety (ISO 26262)
Handshake is preferred over toggle for safety-critical paths.

Handshake Synchronization Limitations
❌ Not for multi-bit data (use FIFO)
❌ Higher latency
❌ Not for very high event rate like back to back control events
Still, it is the most reliable technique for control events.

The Handshake Synchronizer uses a REQ–ACK closed-loop protocol to ensure every event is delivered exactly once across clock domains.
No loss, no duplication, no overwriting — the safest CDC method for control/event transfer.

Multi-Bit CDC — Why Multi-Bit Buses Cannot Use 2-FF Synchronizers

Until now we handled single-bit CDC transfers (levels, pulses, toggles, handshakes)
However, multi-bit data (control and/or real data) behaves completely differently across different clock domains.


Multi-bit transfers will cause new CDC failures to appear:
• Data incoherence
• Bit-skew (between bits of the transferred data0
• Partial updates
• Glitches
• Illegal multi-bit values (incorrect data might get transferred)
• Data corruption

We will now explain why we must NOT synchronize multi-bit buses bit-by-bit using 2-FF.

Why Multi-Bit Signals Cannot Be Treated Like Single-Bit ?


A single bit is simple: only 0 or 1.
But an n-bit bus has 2ⁿ possible values, and transitions do not occur simultaneously.

Example (4-bit bus):
Old value: 0101
New value: 1010
Because of physical routing delays and skew, each bit flips at a slightly different moment.
💥 Destination may capture random mixed combinations like:
• 0001
• 0110
• 1111
• 1011
These values never existed in the source domain → classic data incoherence.

Why 2-FF Synchronizer Fails for Multi-Bit Buses ?

If you blindly apply 2-FF synchronizer for multi-bit busses, then these events will occur in the circuit :

bus[i] → FF1[i] → FF2[i]
Each bit:
• Arrives at a different time
• Experiences metastability differently
• Settles at different speeds
• May be sampled in different cycles
❌ Bits do NOT settle together
❌ Destination captures “half-old, half-new” values
❌ Bus becomes unpredictable
This is the fundamental multi-bit CDC failure.

Real Example of Multi-Bit Capture Failure

Source updates from:
0x0F → 0xF0
Destination might capture:
• 0x07
• 0x8F
• 0xF3
• 0x70
• 0xF8
These are illegal values → can break counters, FSMs, or addresses.

Key Problems in Multi-Bit CDC


• Data Incoherence — bits sampled in different cycles
• Data Corruption — one metastable bit ruins entire word
• Glitches — combinational reconvergence failures
• Bit-Skew — physical delays cause mismatch
• Reconvergence Hazard — different logic paths settle differently
These reasons are why multi-bit data must never be synced bit-by-bit.

CDC Concerns Triggered by Multi-Bit Buses


• Data Corruption — ✔
• Data Incoherence — ✔ Major issue
• Data Loss — ✘ Not typical
• Data Duplication — ✘ Not typical
• Chip Burning — ✔ Possible if a bit goes metastable

What Is the Solution to handle Multi-Bit Busses for CDC ?


Three safe ways to transfer multi-bit data:
⭐ 1. Gray Code Synchronization
For counters & FIFO pointers — only 1 bit changes per update.
⭐ 2. Multi-Bit Handshake Transfer
Source freezes data, destination ACKs when received.
⭐ 3. Asynchronous FIFO
For high-throughput data streams — fully CDC-safe architecture.

Gray Code Synchronization — Safest Way to Transfer Multi-Bit Counters & Pointers

Eariler it was discussed why multi-bit buses cannot use 2-FF synchronizers — because bits change at different times, causing data incoherence and illegal values.
Now we will learn the most reliable solution for a special case:
– When multi-bit values change only 1 bit at a time (This is where Gray Code becomes essential since it only has 1 bit changing at a time)

What is Gray Code?

Gray Code is a numbering system where only one bit changes between consecutive values.
Example (3-bit):
Binary: 000 → 001 → 010 → 011 → 100 → 101 → 110 → 111
Gray: 000 → 001 → 011 → 010 → 110 → 111 → 101 → 100
✔ Binary: many bits flip
✔ Gray: exactly one bit flips

Why Gray Code Is Perfect for CDC ?


The biggest multi-bit CDC issue is:
❌ Destination may capture “mixed” data values
(half old, half new) due to different time delays on the physical paths of each multi-bit


Now if only ONE bit changes for Gray Code:
✔ Even if sampled incorrectly, value is only ±1 away
✔ No illegal transitions
✔ No data incoherence
✔ System stays safe
Gray Code solves multi-bit CDC cleanly.

Most Important Use Case for Gray Code — Asynchronous FIFOs

Async FIFOs use two pointers:
• Read pointer → read clock domain
• Write pointer → write clock domain
Pointers must cross domains safely.
Binary counters change multiple bits → FIFO logic will show :
❌ wrong full
❌ wrong empty
❌ wrong wrap-around
→ leads to data corruption


Gray Code fixes this because:
✔ Only one bit changes
✔ All values are valid
✔ Synchronization of each bit using 2-FF is safe
✔ FIFO full/empty calculations remain correct
This is why all industry async FIFOs use Gray-coded pointers.

How Gray Code Synchronization Works ?


1️⃣ Source domain increments a binary counter
2️⃣ Convert binary → Gray
3️⃣ Send Gray bits across using 2-FF synchronizers
4️⃣ Destination optionally converts Gray → binary
(for pointer comparison)
Since only one bit can change at a time →
no illegal multi-bit combinations can ever appear.

CDC Concerns Solved by Gray Code


• Data Incoherence → ✔ Fully solved
• Data Corruption → ✔ Only 1 bit may change
• Chip Burning → ✔ Lower metastability risk
• Data Loss → ✘ Not relevant
• Data Duplication → ✘ Not related

Why Gray Code Still Works During Metastability ?


If the changing Gray bit becomes metastable:
Destination may capture the previous or next value.
Both are valid Gray values which means FIFO logic remains correct. (There is NO unknown value captured)
This is the beauty of Gray Code.

Where NOT to Use Gray Code ?


Do NOT use Gray Code for:
❌ Arbitrary multi-bit data
❌ Payload data
❌ Addresses
Gray Code is ONLY for:
✔ Sequential pointers
✔ Counters
✔ Async FIFO read/write pointers

Handshake-Based Multi-Bit CDC — Guaranteed Safe Multi-Bit Transfer

In previous sections, we saw why multi-bit buses cannot use 2-FF synchronizers : resampling multi-bits in destination clock domain will result in corrupted data due to physical time delays.
Additionally, we learned Gray Code works only for counters/pointers since only 1 bit changes across CDC boundary. See example block diagram below to see the microarchitecture for this multi-bit handshake logic.


However, how do we handle general multi-bit signals like:
• configuration registers
• status/control bundles
• address + control fields
• instruction words
• mode settings
• multi-bit command words
These cannot use Gray Code (since only 1 bit changes for the counter) → so we need the safest method:
======> Handshake-Based Multi-Bit CDC

Why Multi-Bit Needs a Handshake (Not 2-FF) ?


Synchronizing each bit individually causes:
• Bit skew
• Mixed old+new values
• Invalid combinations
• Partial updates
• Wrong control decisions
• Glitches in logic
=====> Multi-bit data must be frozen during transfer → only handshake guarantees this.

How Multi-Bit Handshake Works ?


The concept is straightforward:
1️⃣ Source freezes and captures the multi-bit data
2️⃣ Source asserts REQ = “Data ready”
3️⃣ Destination receives REQ and captures data
4️⃣ Destination asserts ACK = “Data received”
5️⃣ Source sees ACK, clears REQ
6️⃣ Destination clears ACK → ready for next transfer
✔ All REQ/ACK signals are synchronized using 2-FF
✔ Multi-bit data remains stable until transfer completes

Why This Multibit Handshake Method Is 100% Safe ?


• Source data register is held until ACK is received
• Destination samples all bits coherently in one cycle
• No bit-level changes during crossing
• No partial updates
• No illegal values
• No data corruption
• No duplication or loss
This is the safest method for multi-bit control data.

Use cases for Multi-Bit Handshake CDC


Use it when transferring:
• CPU-to-peripheral configuration
• APB register updates
• Mode change packets
• Sensor control words
• ECC/CNN config registers
• DMA descriptors
• Any multi-bit control bundle requiring accuracy
Multi-bit handshake is used heavily in SoC control paths.

Multbit Handshake CDC Concerns Fixed

Which CDC Concerns are Fixed by Multibit Handshake ?

  • Data Corruption✔ Yes
  • Data Incoherence✔ Yes
  • Data Loss✔ Guaranteed Data
  • Duplication✔ Prevented
  • Chip Burning✔ Controlled (stable signals)

Limitations of Multibit Handshake


❌ Not for high-throughput data
❌ Not for continuous streaming
❌ Not for back-to-back transfers without wait
(Use FIFO for these cases)


Multibit Handshake Summary


Handshake-based multi-bit CDC freezes the data until the destination acknowledges it.
This guarantees no incoherence, no corruption, and perfect multi-bit accuracy across clock domains.

Asynchronous FIFO — The Ultimate CDC Solution for High-Speed Multi-Bit Data

Asynchronous FIFO (Async FIFO) is the most powerful and scalable CDC architecture.


Because it fixes the limitations of all earlier techniques ASYNC FIFO is most powerful but adds more logic and area:
• 2-FF → only single-bit fix
• Toggle → no back-to-back pulses (more latency and slower)
• Handshake → slow & control-only
• Gray Code → only for pointers


Async FIFO is the correct solution when:
⭐ Multi-bit data
⭐ High throughput
⭐ Continuous data flow
⭐ Different clock frequencies
⭐ Bursty traffic
⭐ Streaming interfaces
must cross clock domains safely.

Below is a general block diagram for the Async FIFO

Why Do We Need an Async FIFO?


Other CDC techniques fail for multi-bit and high-speed transfers and back to back transfers:
❌ Multi-bit data
❌ Fast-to-slow or slow-to-fast
❌ Back-to-back transfers
❌ Continuous streaming
Examples:
• Handshake too slow
• Toggle loses events
• Gray works only for counters
• 2-FF cannot sync buses
Async FIFO logic solves all of these but at expense of additional logic and physical area.

What Is an Async FIFO?


It is a dual-clock (write and read clock) memory buffer that allows:
• Writes in write clock domain (wclk)
• Reads in read clock domain (rclk)
Two independent pointers drive it:
• Write pointer (wptr) for write data from memory buffer
• Read pointer (rptr) for read data from memory buffer
===> Both pointers are converted to Gray Code and synchronized across domains, ensuring safe comparison.

Only pointers cross the CDC boundary — not the data. Data is stored in the memory buffer which can be dual port SRAM or flip-flops.

Why ASYNC FIFO Uses Gray Code for Pointers?


Gray code ensures only one bit changes per increment, so:
✔ No illegal pointer combinations
✔ Safe full/empty detection
✔ No multi-bit incoherence
If binary counters were used:
Multiple bits flip → destination may capture garbage → FIFO malfunctions.

How Async FIFO Works ?

Quick Summary :
Write Side
• wptr_bin increments on write operation
• Bin → Gray conversion occurs on write side
• Write Gray pointer crosses to read clk domain and becomes synchronized to read clk domain
• Data stored at RAM address = wptr_bin
Read Side
• rptr_bin increments on read operation
• Bin → Gray conversion occurs on read side
• Read Gray pointer crosses to write clk domain and becomes synchronized to write clk domain
• Data read from RAM address = rptr_bin
Full / Empty Logic
• FIFO Empty: wptr_gray_sync == rptr_gray
• FIFO Full: specific Gray-coded pointer comparison

CDC Concerns Fixed by Async FIFO


CDC Concern Fixed by Async FIFO

  • Data Corruption✔ Yes
  • Data Incoherence✔ Yes
  • Data Loss✔ Yes
  • Data Duplication✔ Yes
  • Chip Burning✔ Controlled
  • Async FIFO is the only CDC method that solves all high-speed multi-bit CDC issues.

Where Async FIFOs Are Used in Real Chips ?


Everywhere on SoC:
• AXI / AHB bridges (different clk domains)
• CPU → Accelerator data pipes
• Memory controllers (dram runs at 2.2GHz and internal may run at 1 GHz)
• SPI/I2C data buffering (due to clock domain differences)
Async FIFOs are foundational in modern SoC design.

Limitations or drawbacks of Async FIFOs


❌ More complex than 2-FF (extra functional logic overhead which takes more area)
❌ Requires dual-port SRAM (instead of a dual-port SRAM one can also use synthesizeable SRAM but this adds area penalty)
❌ Full/empty logic must be correct (need to add extra verification to check full / empty corner cases)
❌ Needs strict CDC verification (also need to ensure timing paths are constrained to not exceed a clock period delay)

It is the most reliable and reusable CDC architecture used in modern SoCs.

Pulse vs Toggle vs Handshake vs FIFO — Which CDC Technique Should You Use?

General topics covered : 2-FF, pulse sync, toggle, handshake, Gray code, multi-bit handshake, and async FIFO

What Are You Transferring?


All CDC signals fall into one of these:
• Single-bit LEVEL (status, enable, mode, fifo full, fifo empty)
• Single-bit PULSE (interrupt, event_done)
• Multi-bit DATA (registers, pointers, payload)
Each category requires a different CDC solution.

Quick Review of the CDC Techniques

Industry decision matrix for CDC methods/techniques


Single-Bit LEVEL Signals → Use 2-FF Synchronizer


Examples:
• enable
• mode
• status
• reset release
✔ Best choice → 2-FF synchronizer
✘ Don’t use toggle, handshake, or FIFO.

Single-Bit PULSE Signals → Toggle or Handshake


Examples:
• interrupt
• timer expiration (timeout)
• dma_req
• error pulse
✔ Pulse rate is low → Toggle Synchronizer
✔ Must NEVER lose a pulse → Handshake Synchronizer
✔ Slow→fast domain → Pulse Stretching
✘ Don’t use 2-FF for pulses.

Multi-Bit Data → Choose Based on Behavior


Examples:
• config registers
• address buses
• mode fields
• FIFO pointers
• sensor data
✔ Counters / pointers → Gray Code
✔ Low-rate multi-bit control → Multi-bit handshake
✔ High-speed continuous data → Async FIFO
✘ Never sync each bit with 2-FF individually.

Quick CDC Decision Guide

Is it multi-bit?
• Yes → Gray code / Handshake / FIFO
• No → Continue
Is it a level or pulse?
• Level → 2-FF
• Pulse → Toggle / Handshake / Pulse stretch

Each CDC technique exists to solve a specific problem:
• 2-FF → single-bit levels
• Toggle → pulses (loss + duplication fixed)
• Handshake → guaranteed event delivery
• Gray code → counters & pointers
• Multi-bit handshake → multi-bit control
• Async FIFO → high-speed multi-bit streams
Choosing the right method is key to building reliable SoCs.

CDC Divergence — Why Async Signals Must Not Fan Out into Multiple Sync Paths

CDC Divergence is a structural CDC hazard that occurs when one asynchronous signal fans out into multiple synchronizers or logic paths in the destination domain.
This is one of the most common and dangerous CDC design mistakes. It can lead to FSM and Interfaces operating incorrectly.

What Is CDC Divergence?


CDC Divergence happens when:
• One async signal → goes to → multiple synchronizers
• One async signal → feeds → multiple logic paths
Example:

┌──> [2-FF Sync A] → Logic A
Async Signal —┤
└──> [2-FF Sync B] → Logic B
Each path samples and resolves metastability independently, leading to mismatched timing.

Why CDC Divergence Is Dangerous ?


Each synchronizer may:
• Resolve metastability at a different time
• Capture the async signal in different cycles
• Drive logic with inconsistent values
This causes:
❌ Glitches
❌ False pulses
❌ Missed events
❌ Conflicting FSM transitions
❌ Broken protocols

Real Example: CDC Divergent IRQ Synchronization


irq_async → Sync_A → FSM A
irq_async → Sync_B → FSM B
Scenario:
• FSM A sees the interrupt earlier
• FSM B sees it later
➡ One FSM begins processing while the other doesn’t → illegal states, wrong sequences, system malfunction.

Glitch Example (Reconvergence Failure)


Async → Sync1 → AND —->– OUT (glitch)
Async → Sync2 → NOT —-
If Sync1 and Sync2 settle in different cycles → output glitches, causing:
• Spurious enable
• False event trigger
• Power spikes
• Data corruption

Why Multiple 2-FF Synchronizers Don’t Fix CDC Reconvergence


Even with 2-FF chains:
• Each chain resolves metastability differently
• Delays and skew differ
• Local placement affects timing
Therefore:
❌ Multiple synchronizers = multiple interpretations of the same async event
❌ Always unsafe

Industry Rule to avoid CDC Reconvergence : One Async Signal → One Synchronizer Only


Correct approach:

Async → [Single 2-FF Sync] → Synced_Signal

├──→ Logic A
├──→ Logic B
└──→ Logic C
✔ All logic receives the same clean signal
✔ No mismatched timing
✔ No divergence hazards

Common Places Where CDC Divergence Happens


You’ll often find divergence issues in:
• Interrupt lines
• Reset deassertion
• Gating enables
• State machine triggers
• Control signals shared between blocks
CDC tools frequently flag these as structural violations.

CDC Concerns Triggered by Divergence

  • CDC ConcernTriggered?
  • Data Corruption✔ Yes
  • Data Incoherence✔ Yes
  • Data Loss✔ Yes
  • Data Duplication✔ Yes
  • Chip Burning✔ Yes (glitch-induced currents)

CDC Convergence — When Multiple Async Signals Enter the Same Logic & Cause Glitches

CDC Convergence occurs when two or more asynchronous signals (or separately synchronized versions of them) feed into the same destination logic.
This is one of the biggest sources of:
• Glitches
• False pulses
• Incorrect state transitions
• Unpredictable combinational outputs

What Is CDC Convergence?


Convergence happens when multiple signals from another clock domain enter the same logic block:

async_A → Sync_A —-AND/OR/MUX → Output
async_B → Sync_B —-
Each synchronized signal settles at different cycles due to:
• Different metastability resolution
• Different routing delays
• Clock skew
• Physical placement differences
This leads to inconsistent logic behavior.

Why Convergence Causes Problems ?


If two signals converge into a logic gate:
✔ One may stabilize earlier
✔ The other one may settle later
The logic gate temporarily sees wrong combinations, creating:
❌ Glitches
❌ False transitions
❌ False interrupts
❌ Invalid FSM decisions

Classic Glitch Example (Most Common)

If Sync_A updates at time T and Sync_B updates at time T+1

a signal ABORT at time T + 0.5000 causes an UNEXPECTED GLITCH at time T+0.5000
This 1-cycle glitch can:
• Fire a false interrupt
• Clock a register incorrectly
• Trigger unwanted FSM transitions


Why Metastability Makes It Worse ?


Even with 2-FF synchronizers:
• Sync_A may resolve metastability after 1 cycle
• Sync_B may resolve after 2 cycles
This mismatch increases glitch probability.



Another Real-World Glitch Example for Convergence


Two control signals entering a MUX selector:

sel_A → Sync_A
sel_B → Sync_B
mux_out = (sel_A & data1) | (sel_B & data2)
If synced signals change at slightly different times:
✔ MUX may briefly choose the wrong data
✔ Temporary corruption occurs
✔ Extremely hard to debug on silicon

How to Avoid Convergence Hazards ?


✔ Solution 1 — Combine Before Syncing
Perform logic operation(s) in source domain and synchronize before sending over to destination clk domain:

combined_async = async_A OR async_B
combined_async → one synchronizer → safe_out
✔ Solution 2 — Use Multi-Bit Handshake
Freeze entire data bundle → capture → ACK → release.
✔ Solution 3 — Use Source-Domain FSM
Do decisions in source domain; send only the final event.
✔ Solution 4 — Sync Once, Then Fan Out
Never sync operands separately.
✔ Solution 5 — Register Logic in Destination
Avoid raw combinational logic fed by multiple CDC inputs.

CDC Concerns Triggered by Convergence


Concern Triggered?

  • Data Corruption✔
  • Data Incoherence✔
  • Data Loss✔
  • Data Duplication✔
  • Chip Burning✔ (due to glitch-induced short currents)

CDC Reconvergence — The Most Dangerous Structural CDC Hazard

CDC Reconvergence is a silent CDC killer.
It occurs when the same async signal is synchronized through multiple paths and later recombined in destination logic.
This leads to glitches, false pulses, mismatched decisions, and extremely hard-to-debug silicon failures.

What Is CDC Reconvergence?



┌──→ Sync A —-\
Async Signal —–┤ AND/OR → OUT
└──→ Sync B —-/
Each synchronizer behaves differently → the same signal appears as different values at the same time.

This is different than convergence as this case has the async signal synchronized in several places and “recombined” later. As we have studied earlier, metastability can cause synchronization to occur at different times leading to incorrect logic signals when sampling in same destination clock domain.


Why Reconvergence Is Dangerous


Because each sync chain resolves metastability on a different cycle:
• Sync A → updates earlier
• Sync B → updates later
This results in:
❌ Glitches
❌ False triggers
❌ Wrong FSM transitions
❌ Incorrect control decisions

Classic Glitch Example for Reconvergence



async → Sync_A –\
OR → out
async → Sync_B –/
If Sync_A updates at T and Sync_B at time T+1,

OR output briefly goes LOW → a false pulse.

A one-cycle glitch can:
• Fire an interrupt
• Clock a register
• Corrupt a data path
• Move an FSM to an invalid state

Most Common Bug Found in Reconvergence : Inverted + Non-Inverted Paths



async → Sync1 → out1
async → NOT → Sync2 → out2
logic = out1 AND out2
Different delays + inversion → guaranteed glitch.
CDC tools flag this as high severity.


How to Fix Reconvergence ?


✔ Synchronize once → fan out the synced signal
✔ Perform logic operations in source domain before crossing (with source clock domain register)
✔ Use multi-bit handshake for grouped signals
✔ Avoid multiple sync paths of the same signal
✔ Use safe encodings (Gray/one-hot) when needed


Correct structure:

Logic A/B/C (source clk domain) → Single Sync (source clk domain) (async crossing) →Single Sync (destination clk domain) → synced_sig → Logic A/B/C (in destination clock domain)


Where Reconvergence Commonly Occurs ?


• Clock gating enables
• Reset release paths
• Interrupt routing
• Mode select logic
• APB/AXI register strobes
• FSM transition conditions


Reconvergence Summary


CDC Reconvergence happens when the same async signal crosses through multiple separate sync paths and then recombines. Because each path stabilizes differently, reconvergence creates glitches, false events, and unpredictable behavior.
Always perform logical operation in source clk domain and synchronize once in source clk domain and fan out the synchronized output in destination clk domain.

Reset Domain Crossing (RDC) — What, Why, When, Where



After CDC data and control paths, the next critical topic is Reset Domain Crossing (RDC) which occurs in every SoC.
Many designs fail not because of data CDC — but because of bad reset release (aka reset deassert).


What Is Reset Domain Crossing (RDC)?


RDC occurs when a reset signal crosses clock domains.
Typically:
• Reset is generated externally or in one source clk domain
• Used by multiple clock domains
• Often asynchronous to destination clocks
NOTE : Reset assertion and deassertion behave very differently.


Why Is RDC Dangerous for SoC ?


✔ Reset assertion (active reset): usually safe due to large setup time
❌ Reset deassertion (release): very dangerous if async bcause hold time to flops can be violated
If reset is released near a clock edge:
• Flops exit reset in different cycles
• Some flops may go metastable
• FSMs start in illegal states
• Counters, handshakes, FIFOs misalign
Result:
❌ Unpredictable boot behavior
❌ Rare, silicon-only failures
❌ Bugs that escape STA & simulation


What Goes Wrong in Bad Reset Release?


• Partial reset of logic
• FSM bits not aligned (incorrect FSM reset state)
• Counters start incorrectly
• CDC paths break at startup
• Control logic behaves randomly (interfaces may lockup and not operate)
This is one of the hardest bugs to debug in real chips.


When & Where RDC Violations Occur ?


RDC exists whenever:
• One reset drives multiple clock domains (reset delay skew will cause some logic to reset earlier or later than others)
• Reset comes from external pin / PMU
• POR, warm reset, watchdog reset
• Reset enters clock-gated logic
• Reset crosses power domains
• Reset feeds CDC synchronizers or FIFOs
➡ In modern SoCs, RDC is unavoidable.


Golden Rule of GOOD Reset Design


Async Assert, Sync Deassert
• Assert reset asynchronously → fast & safe
• Deassert reset synchronously → clean & aligned
This rule exists only to prevent RDC failures.


Safe Reset Concept (High-Level)

Wrong:

always_ff @(posedge clk or negedge rst_n)
(Async deassert → unsafe)


Correct idea:

rst_async → reset synchronizer → rst_sync_n
Use rst_sync inside the clock domain so all flops exit reset together.

always_ff @(posedge clk or negedge rst_sync_n)


CDC vs RDC (Important Use Cases to Know)


• CDC → data/control signals
• RDC → reset signals
• CDC risk → metastability, glitches
• RDC risk → partial reset, illegal states
CDC tools treat RDC as a separate, high-severity check.


RDC Summary


Reset Domain Crossing (RDC) happens when reset signals cross clock domains.
While reset assertion is usually safe, asynchronous reset deassertion can break the entire design through partial reset and illegal states.
The golden rule: Async assert, sync deassert.

Safe Reset Synchronizer Design — Async Assert, Sync Deassert

How do we design reset logic correctly in multi-clock SoC systems?

The Golden Rule of Reset Design : Async Assert, Sync Deassert


• Reset assertion must be immediate (force flip-flops to known states immediately)
• Reset deassertion must be clock-aligned (must satisfy flip-flop reset hold times)
Breaking this rule is the #1 cause of RDC failures.


Why Reset Deassertion Must Be Synchronized ?


If reset is released asynchronously:
• Some flops exit reset earlier
• Some exit later
• Some go metastable
Result:
❌ FSM illegal states
❌ Counters start wrong
❌ CDC handshakes break
❌ FIFO pointers misalign

❌ Interfaces can lock up and not respond

So reset release must behave like a CDC control signal.


Standard Reset Synchronizer (Per Clock Domain)


Conceptual structure:

rst_async
|
[FF1] ← async assert
|
[FF2] ← sync deassert
|
rst_sync → used inside domain


✔ Both FFs clocked by destination clock
✔ Reset asserts immediately
✔ Reset releases after 2 clean clock edges
✔ All flops exit reset together


One Reset Doesn’t Mean One Synchronizer


Important rule:

Global Reset
├─→ Reset Sync (clk_A)
├─→ Reset Sync (clk_B)
└─→ Reset Sync (clk_C)
✔ Each clock domain needs its own reset synchronizer
❌ Never share a synchronized reset across domains


Reset + Clock Gating (Common Bug)


❌ Reset released while clock is gated OFF
→ Some flops never see reset deassertion
✔ Ensure clock is ungated during reset release
✔ Or release reset before enabling clock gating


Reset in CDC Structures (Critical)


• 2-FF sync → both FFs reset identically
• Toggle / Handshake → reset protocol to idle
• Async FIFO →
– write reset resets write pointer
– read reset resets read pointer
– FIFO must start EMPTY
Bad FIFO reset = silent data corruption.


Common RDC Mistakes


❌ Using async reset everywhere
❌ Sharing reset sync across domains
❌ Reset used as control/data
❌ Reset released under gated clock
❌ Reset after combinational logic
All are high-severity RDC violations.


RDC Summary


Safe reset design follows one rule:
Asynchronous assert, synchronous deassert — per clock domain.
Using a local reset synchronizer in each clock domain ensures clean startup, prevents metastability, and avoids partial reset failures.
RDC correctness is as critical as CDC correctness.

Reset Domain Crossing (RDC) — A Common Interview & Design Question



This is one of the most frequently asked RDC questions in interviews and a real design decision engineers must get right.

Interview Question
When a reset crosses clock domains, we use a 2-FF reset synchronizer.
But those two flip-flops themselves need a reset.

Which reset should be used for the 2-FF synchronizer flops?


✅ Correct Answer
Use the SAME asynchronous reset that is being synchronized to reset the 2-FF synchronizer flops.
This is intentional, safe, and industry-standard.
🔸 Why This Is Correct (Key Reasoning)
• Reset often comes from POR / PMU / external pin → asynchronous
• Reset assertion must be immediate, regardless of clock
• Reset deassertion must be clean and clock-aligned
The 2-FF synchronizer’s job is only to make reset deassertion synchronous.
So:
• Both synchronizer FFs
– are clocked by the destination clock
– use the raw async reset on their reset pins
• Output of FF2 becomes rst_sync
• rst_sync is used by all logic in that clock domain
This guarantees:
✔ Async assert
✔ Sync deassert
🔸 Critical Design Rules (Interview Gold)
❌ Never reset the synchronizer flops using the synchronized reset
→ This creates a chicken-and-egg problem.
✔ One reset synchronizer per clock domain
❌ Never reuse a reset synchronized to clk_A in clk_B
✔ Only the reset synchronizer sees async reset
All other logic must use only the synchronized reset
🔸 Correct Reset Structure (Conceptual)

rst_async
|
[FF1] ← async reset, clk = dest_clk
|
[FF2] ← async reset, clk = dest_clk
|
rst_sync → used by all logic in this domain

🔸 Key Takeaway
⭐ Reset assertion is asynchronous
⭐ Reset deassertion must be synchronous
That is exactly why the reset synchronizer flops are reset using the same async reset they synchronize.


🔸 Why This Question Matters for RDC ?
• Very common CDC/RDC interview question
• Flagged by SpyGlass RDC
• Critical for FSM stability
• Mandatory for ISO 26262 designs
• Wrong design causes rare silicon-only failures

Reset Domain Crossing (RDC) — Real Failure Scenarios & Debugging

RDC bugs are among the hardest failures to debug because they often:
• Do not appear in simulation
• Fail only occasionally
• Appear only on silicon
• Are highly timing-dependent


Below are real RDC failures seen in industry.

Partial Reset Release Failure


❌ Problem
Reset deasserted asynchronously → flops exit reset in different cycles.
🔥 Symptoms
• FSM illegal states
• Random register values
• Boot sometimes passes, sometimes fails
🔍 Root Cause
No reset synchronizer.
✅ Fix
Async assert, sync deassert.
One reset synchronizer per clock domain.



Reset Used as Data / Enable Failure


❌ Problem
Reset reused as enable, qualifier, or control.
🔥 Symptoms
• Glitches during reset release
• Counters increment in reset
• Unexpected enables, power spikes
🔍 Root Cause
Reset treated as functional signal.
✅ Fix
Reset only resets.
Generate separate synchronized control signals.

Shared Reset Synchronizer Across Domains Failure


❌ Problem
One synchronized reset reused for multiple clocks.
🔥 Symptoms
• One domain boots correctly
• Another behaves randomly
• Handshakes fail at startup
🔍 Root Cause
Reset synced to clk_A used in clk_B.
✅ Fix
One reset synchronizer per clock domain.

Reset Released While Clock Is Gated Failure

❌ Problem
Reset released when clock is OFF.
🔥 Symptoms
• Block stuck in reset
• Logic never activates
🔍 Root Cause
Clock gating active during reset release.
✅ Fix
Ungate clock during reset release
or release reset before clock gating.

Async FIFO Reset Misalignment Failure


❌ Problem
Read/write resets not coordinated and possibly reset with incorrect clock domain(s).
🔥 Symptoms
• FIFO not EMPTY after reset
• Invalid first data
🔍 Root Cause
Pointers not reset per domain.
✅ Fix
Reset write pointer in write domain
Reset read pointer in read domain
Verify EMPTY/FULL after reset.

Reset Reconvergence Failure


❌ Problem
Reset synchronized multiple times and reconverges.
🔥 Symptoms
• Reset glitches
• False reset pulses
• Random reinitialization
🔍 Root Cause
Multiple reset sync paths.
✅ Fix
Single reset synchronizer per domain.
Fan out only the synchronized reset.

Why RDC Bugs Escape Simulation ?


• Zero-delay RTL hides timing
• Reset release rarely stressed
• No metastability modeling
• Power-up sequencing ignored
➡ CDC/RDC tools are mandatory.

How RDC Is Debugged in Industry


✔ CDC/RDC tools (SpyGlass RDC)
• Async deassert checks
• Reset fanout & reconvergence
• Reset + clock gating issues
✔ Silicon bring-up
• Boot loops
• Power cycling
• Temp / voltage stress
✔ Waveform analysis
• Reset vs clock
• FSM state after reset
• FIFO pointers


RDC Key Takeaway


RDC failures are rare, system-wide, and catastrophic.
Correct RDC design —> async assert, sync deassert, one reset synchronizer per domain — is non-negotiable and golden standard.

SOC design with CDC

As an SOC may easily have ten or more clock domains one may easily overlook connections in the RTL code (Verilog/Systemverilog). Thus, it’s the role of CDC to catch these escape errors or bugs that occur during the development stage and not let it propagate it into simulation or real silicon.

CDC Divergence — Why Async Signals Must Not Fan Out into Multiple Sync Paths

✔ Solution 1 — Combine Before Syncing
Perform logic operation(s) in source domain and synchronize before sending over to destination clk domain:

combined_async = async_A OR async_B
combined_async → one synchronizer → safe_out
✔ Solution 2 — Use Multi-Bit Handshake
Freeze entire data bundle → capture → ACK → release.
✔ Solution 3 — Use Source-Domain FSM
Do decisions in source domain; send only the final event.
✔ Solution 4 — Sync Once, Then Fan Out
Never sync operands separately.
✔ Solution 5 — Register Logic in Destination
Avoid raw combinational logic fed by multiple CDC inputs.

CDC Reconvergence – Why Async Signals Must Not Fan Out and Fan back to One Path

CDC Reconvergence happens when the same async signal crosses through multiple separate sync paths and then recombines. Because each path stabilizes differently, reconvergence creates glitches, false events, and unpredictable behavior.
Always perform logical operation in source clk domain and synchronize once in source clk domain and fan out the synchronized output in destination clk domain.

SOC design with RDC

RDC (reset domain crossing) is a special case of CDC applied to resets.

RDC failures are rare, system-wide, and catastrophic.
Correct RDC design —> async assert, sync deassert, one reset synchronizer per domain — is non-negotiable and golden standard.

Understanding Sampling, Quantization, and Signal Quality

June 1, 2026

Sampling, Quantization, FFT, SNR, THD

Sampling refers to the rate of sampling an analog signal. In order to reconstruct an analog signal in the digital domain, one needs to operate the digital frequency at least twice the analog frequency to maintain correct capture of the analog signal. This is also called the Nyquist frequency. So for example, if the analog signal like audio frequency is 64kHz, the minimum digital frequency would be 128kHz to correctly capture this audio signal. The diagram below describes why one needs twice the frequency.

Quantization errors occur when converting analog signals into discrete digital values. Because digital systems have limited precision, continuous values must be rounded or truncated to the nearest available bit level. This inherent mismatch creates distortion and adds a “noise floor” to the processed signal.

Types of Quantization Errors

  • Input/Output Quantization: The initial loss of precision when an Analog-to-Digital Converter (ADC) maps continuous voltages to discrete binary levels.
  • Coefficient Quantization: In digital filters, the ideal filter coefficients (calculated with floating-point math) must be rounded to fit fixed-point hardware registers.
  • Product Round-off & Overflow: Mathematical operations like multiplication and addition expand the bit length of a signal, often requiring rounding or truncation between processing stages.

Key Impacts

  • Quantization Noise: Appears as a wideband noise (e.g., hiss in audio) or harmonic distortion, which limits the system’s dynamic range.
  • Limit Cycles: In recursive (IIR) filters, round-off errors can trap the output signal in persistent, low-level oscillations, even when the input is zero

How to Minimize the Errors

  • Increase Bit Depth: Moving from an 8-bit to a 16-bit or 24-bit system increases the number of available quantization levels, significantly reducing the step size and error. [1, 2]
  • Dithering: Adding a tiny, controlled amount of random noise to the analog signal prior to quantization disrupts the correlation between the signal and the error. This converts harsh, deterministic distortion into benign white noise.
  • Noise Shaping: A technique used in oversampling converters (like Delta-Sigma ADCs) that pushes quantization noise out of the frequencies of interest and into higher, inaudible ranges.

SNR is the metric used across science and engineering that compares the power of a desired signal versus the level of background noise (unwanted interference). A larger or higher SNR generally indicates a clearer more accurate and more reliable signal.

How SNR works:

  • Concept: It measures the separation between the legitimate signal (like a singer’s voice or a data transmission) and the noise floor (like electrical hum or static).
  • The Math: SNR is calculated as the ratio of signal power to noise power. Because this ratio can be very large or very small, it is typically expressed in decibels (dB).
  • General Rule: An SNR greater than \(0\text{ dB}\) (or a ratio higher than \(1:1\)) means the desired signal is stronger than the background noise. [1, 2]

Typical Applications

  • Audio & Music: A high SNR (e.g., \(95\text{ dB}\) or higher) in an amplifier or microphone means the music or vocals will be crisp and clear, with minimal background hiss.
  • Wireless & Networking: In Wi-Fi or cellular networks, an SNR of at least \(25\text{ dB}\) is typically required for good, reliable connectivity, while anything below \(10\text{ dB}\) usually results in a poor or dropped connection.
  • Imaging: In photography and sensor technology, a higher SNR results in sharper images with less visual “grain” or noise.

Equation

SNR (dB) = Signal (in dB) – N (in dB)

How the FFT Works

  • Time to Frequency Conversion: A standard Discrete Fourier Transform (DFT) is mathematically intensive. The FFT is a fast, computationally optimized method to calculate the DFT, changing the complexity from (O(N^2)) to (O(N log N)).
  • Decomposition: The algorithm breaks an \(N\)-point time-domain signal into shorter segments, calculates the spectrum for each, and synthesizes them using a “butterfly” computational element.
  • Output Data: The output consists of complex numbers that define both the amplitude (magnitude) and phase of every frequency component within the signal.

Key Applications

  • Audio & Acoustics: Used heavily for audio compression, noise reduction, and analyzing vocal or musical signals.
  • Electronics & Communications: Helps troubleshoot noise, harmonic distortion, and signal interference in oscilloscopes and spectrum analyzers.
  • Vibration & Structural Analysis: Detects wear or imbalances in mechanical machinery by identifying dominant frequency spikes.

Common Processing Considerations

  • Sampling Rate: Per the Nyquist theorem, your sampling frequency must be at least twice the maximum frequency you want to measure to avoid aliasing.
  • Spectral Leakage: Because signals are observed over finite time windows, boundaries can create artificial frequency components. Windowing functions (like Hamming or Hanning) are often applied prior to the FFT to minimize this.

THD (Total Harmonic Distortion) in signal processing is a metric used to measure the amount of unwanted harmonic distortion in a signal relative to its original harmonic frequency. It evaluates how much an output waveform has deviated from the ideal input waveform due to non-linearities in a system.

A lower THD indicates a more accurate and higher-fidelity signal, while a higher THD means the signal has been more heavily altered or distorted.

How THD is Calculated

Mathematically, THD is defined as the ratio of the total power (or root mean square, RMS) of all the harmonic frequencies combined to the power (or RMS) of the fundamental frequency.

THD(V)=sqrt(V22+V32+V42+...+Vn2)/V1100THD(V) = sqrt(V2^2 + V3^2 + V4^2 + … + Vn^2) / V1 * 100%
  • (V1) is the RMS voltage (or current) of the fundamental frequency.
  • (V2, V3, V4), etc., are the RMS voltages (or currents) of the subsequent harmonics.

THD is typically expressed as a percentage (e.g., (0.01%)) or in decibels (dB) (e.g., (-80dB).

Where THD Matters

  • Audio Systems: In speakers, microphones, and amplifiers, a low THD guarantees the equipment reproduces music or voice accurately. An ideal audio amplifier aims to have a THD close to zero, whereas a THD over a few percent might be noticeable to the listener.

SystemVerilog and Verilog Coding Examples

July 6, 2023
  • Simple data flip flop example
  • Combination logic using continuous assignment example
  • FSM to detect serial 10110
  • FSM : Traffic Controller
  • FSM : Round Robin Arbiter (used for QoS)
  • IEEE Floating Point : Single Precision
  • FIFO Depth Calculation
  • Systemverilog Asstion to check analog connections
  • Systemverilog Synchronous FIFO
  • Systemverilog Asynchronous FIFO
  • Rate Adapter using Valid/Ready Handshake

Simple data flop flop example

module dff (data, clock, q);

input data;

input clock;

output q:

reg q;

always @(clock)

q <= data;

end

endmodule

Combinational logic using continuous assignment example

Think of what decoding you want like an address decoding for all bits to 0 and then create logic 1

assign address_dec = (address[3:0] == 4’b0000) 1 ? 0 ; //output 1 when all address bits are 0 else output 0

FSM (finite state machine) example : serial bit stream algorithm to detect start with “1” 10110 and end with “0”.

Step 1: Define states and transitions

Start with reset which becomes State 0 (S0)

S0 : If data == 0 then loop back to S0

else if data == 1 then transition to S1 [1]

S1 [1] : if data == 0 then transition to S2 (detected pattern [10])

else if data == 1 then loop back to S1 (detected a [1])

S2 [10] : if data == 0 then transition to S0 (detected 100 pattern which doesn’t exist so start all over a wait for a 1 input)

else if data == 1 then transition to S3 (detected pattern [101])

S3 [101] : if data == 0 then transition to S2 (detected 1010 pattern which is basically [10])

else if data == 1 then transition to S4 (detected pattern [1011])

S4 [1011] : if data == 0 then transition to S2 (possible [10] pattern) (detected pattern [10110]) output DETECT signal

else if data ==1 then transition to S1 (detected [1] pattern)

Step 2 – Convert FSM to verilog

to be added

FSM example : traffic controller algorithm

Background context : Traffice controller controls the main highway which has a green light by default since it’s the main thoroughfare. The perpendicular road aka farm road requires any crossing traffic to be detected by a “sensor”. In the meantime, it must wait at the farm red light. It is assumed the output for each set of lights : highway and farm is driving a set of 3 lights : red, yellow, green. An active high drives the light for each light. We can add assertions that only one light is active for each direction : highway and farm. There can never be more than one active high signal for each output.

Step 1 – Define states and transition states
// traffic controller
// assume red,yellow,green light for highway East/West
// this becomes the out_fast[2:0] = red,yellow,green
// assume red,yellow,green light for road North/South
// this becomes the out_slow[2:0] = red,yellow,green
// assume highway has priority and always green
// assume sensor is on road North and South side to detect car

module traffic_controller (clk, sensor, reset, out_fast, out_slow);
input clk;
input sensor;
input reset;
output reg [2:0] out_fast; // red, yellow, green
output reg [2:0] out_slow; // red, yellow, green

// 6 possible states but can be reduced
// fast_green
// fast_yellow
// fast_red
// slow_green
// slow_yellow
// slow_red

// or since fast_green means slow_red
// we can encode the states into 4 instead of 6 states
// assume fast_green and slow_red = 2’b00
// assume fast_yellow and slow_red = 2’b01
// assume fast_red and slow_green = 2’b10
// assume fast_red and slow_yellow = 2’b11
parameter FGREEN_SRED = 2’b00;
parameter FYELLOW_SRED = 2’b01;
parameter FRED_SGREEN = 2’b10;
parameter FRED_SYELLOW = 2’b11;

reg [1:0] current_state, next_state;

// next state
always @(*)
begin
case(current_state)
FGREEN_SRED: begin
out_fast = 3’b001; // green highway
out_slow = 3’b100; // red farm
if (sensor) // sensor detects vehicle on farm road then turn light on highway to yellow
next_state = FYELLOW_SRED;
else
next_state = FGREEN_SRED; // keep green highway and red farm
end
FYELLOW_SRED: begin
out_fast = 3’b010; // yellow highway
out_slow = 3’b100; // red farm
// change to next state of Fast red and slow green
next_state = FRED_SGREEN;
end
FRED_SGREEN: begin
out_fast = 3’b100; // red highway
out_slow = 3’b001; // green farm
// change to next state of Fast red and slow yellow
next_state = FRED_SYELLOW;
end
FRED_SYELLOW: begin
out_fast = 3’b100; // red highway
out_slow = 3’b010; // yellow farm
// change to next state of fast green and slow red
next_state = FGREEN_SRED;
end
default: next_state = FGREEN_SRED;
endcase
end

// current state

always @(posedge clk or posedge reset)
begin
if (reset)
current_state <= 3’b000;
else
current_state <= next_state;
end

endmodule

module main;
reg clk = 0;
reg reset = 1;
reg sensor = 0;
wire [2:0] out_fast, out_slow;

// instantiate traffic controller

traffic_controller itc (clk, sensor, reset, out_fast, out_slow);

initial
begin
#5 clk = !clk;
end

initial
begin
$display(“Hello, World: It’s zero time”, $time);
# 1 ; $display(“clk = %b, out_fast = %b, out_slow = %b, time = %0t”, clk, out_fast, out_slow, $time);
# 1 ; $display(“clk = %b, out_fast = %b, out_slow = %b,time = %0t”, clk, out_fast, out_slow, $time);
# 5 ; $display(“clk = %b, out_fast = %b, out_slow = %b,time = %0t”, clk, out_fast, out_slow, $time);
$finish ;
end
endmodule

Round Robin Priority Arbiter

Background context and assumptions:

Assume 4 bits input request “req” and 4 bits output grant “gnt”

Assume clock input “clk” and reset “rst” which is active high.

There must a FSM to store a pointer to last grant.

Increment pointer in round robin from 00 to 01 to 10 to 11 if request occurred

req[0] means grant = 0001 then increment pointer = 01

req[1] means grant = 0010 then increment pointer = 10

req[2] means grant = 0100 then increment pointer = 11

req[3] means grant = 1000 then increment pointer = 00

module round_robin_arbiter (

           input wire clk,

           input wire reset,

           input wire [3:0] req,

           output reg [3:0] grant);

// pointer tracks index for last highest priority requester for current cycle

reg [1:0] pointer;

// seq logic pointer

always @(posedge clk or posedge reset) begin

        if (reset) begin

           pointer <= 2’b00; // start with req[0] having highest priority

        end else begin

        // update / increm pointer only when a grant is issued

        if (|req) begin // pointer should move to next position after one is granted

            case (1’b1) // reverse case and used for priority encoder or one hot

                grant[0] : pointer <= 2’b01; // if current grant[0] then next highest priority

                grant[1] : pointer <= 2’b10; // if current grant[1] then next highest priority

                grant[2} : pointer <= 2’b11; // if current grant[2] then next highest priority

                grant[3] : pointer <= 2’b00; // if current grant[3] then round robin back to 2’b00

               default: pointer <= pointer ; // keep pointer if no one is granted

            endcase

          end

         end

   end

// comb logic to determine grant for current cycle

// based upon current pointer and req signal

always @(*) begin

        grant = 4’b0000; // default no grant and avoid inferred latch

        // start checking current pointer position in a round robin manner

// priority order : pointer, point + 1

        case pointer

            2’b00: begin

               if (req[0]) grant = 4’b0001; // highest priority

               else if (req[1]) grant = 4’b0010;

               else if (req[2]) grant = 4’b0100;

                else if (req[3]) grant = 4’b1000;

              end

        2’b01: begin

            if (req[1]) grant = 4’b0010;

            else if (req[2]) grant = 4’b0100;

            else if (req[3]) grant = 4’b1000;

            else if (req[0]) grant = 4’b0001;

         end

        2’b10 : begin

           if (req[2]) grant = 4’b0100;

          else if (req[3]) grant = 4’b1000;

          else if (req[0]) grant = 4’b0001;

          else if (req[1]) grant = 4’b0010;

        end

        2’b11: begin

           if (req[3]) grant = 4’b1000;

          else if (req[0]) grant = 4’b0001;

          else if (req[1]) grant = 4’b0010;

          else if (req[2]) grant = 4’b0100;

         end

     endcase

  end

endmodule

IEEE 754 Floating Point Standard – Single Precision

IEEE 754 Floating Point Standard

·         IEEE has developed a standard for both 32 and 64 bits floating point representation

·         The standard was targeted to be used in Personal Computer (IBM-type PC and Apple Macintosh)

·         Apple Macintosh also provides its own 80-bit format

·         IEEE 754 defines a 32-bits format called single-precision floating point format

o        Leftmost bit is the mantissa sign (0 for positive and 1 for negative)

o        Followed by 8 bits exponent

o        Followed by 24 bit mantissa (23 bits + implied which is always assumed to be 1)

o        Exponent is represented using Excess-127 which gives an exponent range of: 2-126 to 2+127

Exponents 0 (2-127) and 255 (2+128) are reserved for special use

o        Implied exponent base is 2

o        Fraction point position is to right of the leading mantissa bit

o        Special numbers (e.g. 0, ∞, very small none normalized numbers, etc.) are supported

o        Supported precession is approximately 7 decimal significant digits

o        Allows for approximate range of 10-45 to 10+38

·         IEEE 754 defines a 64-bits format called double-precision floating point format

o        It works similar to the single-precision format

o        11 bits for exponent and 52 bits for mantissa

o        Supported precession is approximately 15 decimal significant digits

o        Allows for approximate range of 10-300 to 10+300

Convert Decimal Real Number to IEEE 754 Floating Point Format

·         The following steps provide the method to convert a decimal real number to IEEE 754 Floating Point format:

o        Convert the decimal number to binary

o        Adjust binary point to proper position

o        Normalize the number

o        Convert exponent from sign-and-magnitude to Excess-127

o        Convert exponent to binary

o        Store the number in the floating point format

1. Convert 36.510 to single-precision IEEE 754 floating point format

1. Convert to binary                                                     = 100100.1

2. Adjust binary point to proper position                                = 1.001001 x 25

3. Normalize                                                                     already normalized

4. Convert exponent to Excess-127                           = 127 + 5 = 132

5. Convert exponent to binary                                   = 10000100

6. Store in floating point format                 = 0 10000100 00100100000000000000000

2. Convert –0.25 to single-precision IEEE 754 floating point format

1. Convert to binary                                                     = .01

2. Adjust binary point to proper position                                = 0.1 x 2-1

3. Normalize                                                                   = 1.0 x 2-2

4. Convert exponent to Excess-127                           = 127 – 2 = 125

5. Convert exponent to binary                                   = 01111101

6. Convert to floating point format                            = 1 01111101 00000000000000000000000

Convert from IEEE 754 Floating Point Format to real number

·         The following steps provide the method to convert IEEE 754 Floating Point to decimal real number:

o        Convert exponent from binary to decimal

o        Convert from Excess-127 to sign-and-magnitude

o        Convert to exponent notation

o        Remove exponent (if possible)

o        Convert from binary to decimal real number

1. Convert 1 01111101 00000000000000000000000 to decimal real number

1. Convert exponent to decimal                                 = 125

2. Convert Excess-127 to sign-and-magnitude        = 125 – 127 = -2

3. Convert to exponent notation                                = – 1.0 x 2-2

4. Remove exponent                                                    = – 0.01

5. Convert to decimal real number                             = – 0.25

2. Convert 0 10000001 11001100000000000000000 to decimal real number

1. Convert exponent to decimal                                 = 129

2. Convert Excess to Exponent                  = 129 – 127 = 2

3. Convert to exponent notation                                = 1.110011 x 22

4. Remove exponent                                                    = 111.0011

5. Convert to decimal real number                             = 7.1875

Floating Point Number Representation

Floating point numbers are used to represent noninteger fractional numbers and are used in most engineering and technical calculations, for example, 3.256, 2.1, and 0.0036. The most commonly used floating point standard is the IEEE standard. According to this standard, floating point numbers are represented with 32 bits (single precision) or 64 bits (double precision).

In this section, we will look at the format of 32-bit floating point numbers only and see how mathematical operations can be performed with such numbers.

According to the IEEE standard, 32-bit floating point numbers are represented as follows:

3130      2322                0 0 0XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX↑↑↑signexponentmantissa

The most significant bit indicates sign of the number, where 0 indicates positive and 1 indicates negative.

The 8-bit exponent shows the power of the number. To make the calculations easy, the sign of the exponent is not shown, but instead excess 128 numbering system is used. Thus, to find the real exponent, we have to subtract 127 from the given exponent. For example, if the mantissa is “10000000,” the real value of the mantissa is 128 − 127 = 1.

The mantissa is 23 bits wide and represents the increasing negative powers of 2. For example, if we assume that the mantissa is “1110000000000000000000,” the value of this mantissa is calculated as follows: 2−1 + 2−2 + 2−3 = 7/8.

The decimal equivalent of a floating point number can be calculated using the following formula:

Number=(−1)s 2**(e−127) 1⋅f,where s=0 for positive numbers, s=1 for negative numbers,e=exponent (between 0 and 255), and f=mantissa.

As shown in the above formula, there is a hidden “1” before the mantissa; i.e., mantissa is shown as “1 · f.”

The largest and the smallest numbers in 32-bit floating point format are as follows:

0  11111110  11111111111111111111111

This number is (2 − 2−23) 2127 or decimal 3.403 × 1038. The numbers keep their precision up to six digits after the decimal point.

0  00000001  00000000000000000000000

This number is 2−126 or decimal 1.175 × 10−38.

4bit ALU example

to be added

Signed adder with two different bit lengths example

to be added

4×4 unsigned Multiplier example

to be added

4×4 signed multiplier example

to be added

FIFO depth calculation

How deep does a FIFO need to be to prevent overflow or underflow?

This scheme is based upon maximum INGRESS (input) rate and maximum OUTGRESS (drain) rate

First establish the maximum INGRESS (input) rate

Second, determine the maximum OUTGRESS (drain) rate

Third, the difference between INGRESS – OUTGRESS becomes the FIFO DEPTH needed to prevent overflow.

INGRESS : can be interpreted as maximum write burst

OUTGRESS : can be interpreted as ((maximum write burst size) x write clk period ) / read clk period

GIVEN : Input FREQ A is 1/4 Output FREQ B

Period B enable = Period A enable * 100 (this becomes the BURST LENGTH)

or turned around 100 Period A enables are bursting to become one Period B enable

Duty Cycle B = 25% (this means reading is performed 1/4 of total burst time)

Assuming clk_B = 100 MHz or 10ns period, then clk_A = 100MHz/4 = 25 MHz or 40ns

So total write BURST time = 40 ns * 100 = 4000 ns

So total read BURST time = 1/4 of total write BURST time = 4000/4 = 1000 ns

So FIFO should be able to hold for 4000 – 1000 = 3000 ns

So number of data items can be read in a period of 3000 ns = 3000 ns/40ns = 75

So minimum depth of the FIFO should be 75.

SYSTEMVERILOG ASSERTION TO FORMALLY CHECK CONNECTIONS

always_comb

con_check: assert #0 ianalog.bity==ireg.bitx; else $error(“Connectivity from register bit x to analog input bity is not satisfied”);

FIFO synchronous example

This design parameters both the Data Width (DATA_WIDTH) and the FIFO Depth (FIFO_DEPTH). It automatically calculates the required address pointer width using $clog2, handles full/empty flags, and prevents data corruption from overflow or underflow.

Example fifo block diagram and sample waveform is shown below from Gemini generation

`timescale 1ns / 1ps

module sync_fifo #(
parameter int DATA_WIDTH = 8, // Width of the data bus
parameter int FIFO_DEPTH = 16 // Depth of the FIFO (should be power of 2)
)(
input logic clk, // Clock signal
input logic rst_n, // Active-low synchronous reset

// Write Interface
input logic wr_en, // Write enable
input logic [DATA_WIDTH-1:0] wr_data, // Data to be written
// Read Interface
input logic rd_en, // Read enable
output logic [DATA_WIDTH-1:0] rd_data, // Data to be read
// Status Flags
output logic full, // FIFO is full
output logic empty // FIFO is empty

);

// Calculate pointer width. Extra bit is used to distinguish between Full and Empty.
localparam int ADDR_WIDTH = $clog2(FIFO_DEPTH);
// Memory array (Inferred RAM)
logic [DATA_WIDTH-1:0] fifo_mem [0:FIFO_DEPTH-1];
// Read and Write Pointers (include an extra MSB bit for wrap-around detection)
logic [ADDR_WIDTH:0] wr_ptr;
logic [ADDR_WIDTH:0] rd_ptr;
// --- Status Flags Logic ---
// Empty when pointers are completely identical
assign empty = (wr_ptr == rd_ptr);
// Full when the lower address bits match, but the MSB (wrap-around bit) is different
assign full = (wr_ptr[ADDR_WIDTH-1:0] == rd_ptr[ADDR_WIDTH-1:0]) &&
(wr_ptr[ADDR_WIDTH] != rd_ptr[ADDR_WIDTH]);
// --- Write Operation ---
always_ff @(posedge clk) begin
if (!rst_n) begin
wr_ptr <= '0;
end else if (wr_en && !full) begin
fifo_mem[wr_ptr[ADDR_WIDTH-1:0]] <= wr_data;
wr_ptr <= wr_ptr + 1'b1;
end
end
// --- Read Operation ---
always_ff @(posedge clk) begin
if (!rst_n) begin
rd_ptr <= '0;
rd_data <= '0;
end else if (rd_en && !empty) begin
rd_data <= fifo_mem[rd_ptr[ADDR_WIDTH-1:0]];
rd_ptr <= rd_ptr + 1'b1;
end
end

endmodule

Architectural Highlights

  • Wrap-Around Pointer Method: Instead of keeping track of a continuous counter (count == FIFO_DEPTH), this design uses pointers that are 1 bit wider than needed for the address space (ADDR_WIDTH + 1).
    • If wr_ptr and rd_ptr are exactly equal, the FIFO is empty.
    • If wr_ptr has looped around (MSB is different) but the lower indices match rd_ptr, the FIFO is full.
  • Safety Guards: The write logic (wr_en && !full) and read logic (rd_en && !empty) ensure that accidental assertions of read/write enables during boundary states are ignored, preventing data corruption.
  • Inferred Memory: The fifo_mem structure is written cleanly so modern synthesis tools (like Vivado or Quartus) can easily map it directly to block RAM (BRAM) or distributed RAM depending on the sizing.

Example Testbench Module

Here is a simple testbench code snippet to demonstrate how you instantiate this module with custom parameters:

module tb_sync_fifo;
// Instantiate with custom width (16-bit) and depth (8 slots)
localparam int W = 16;
localparam int D = 8;

logic clk;
logic rst_n;
logic wr_en;
logic [W-1:0] wr_data;
logic rd_en;
logic [W-1:0] rd_data;
logic full;
logic empty;
// UUT Instance
sync_fifo #(.DATA_WIDTH(W), .FIFO_DEPTH(D)) uut (.*);
// Clock Generator (100MHz)
always #5 clk = ~clk;
initial begin
clk = 0;
rst_n = 0;
wr_en = 0;
rd_en = 0;
wr_data = 0;
#20 rst_n = 1; // Release reset
// Write Data to FIFO
@(posedge clk);
wr_en = 1; wr_data = 16'hAAAA;
@(posedge clk);
wr_data = 16'hBBBB;
@(posedge clk);
wr_en = 0;
// Read Data from FIFO
@(posedge clk);
rd_en = 1;
@(posedge clk);
rd_en = 0;
#50 $finish;
end

endmodule

FIFO asynchronous example

Here is a complete, robust, and fully synthesizable Asynchronous FIFO implementation in SystemVerilog.

Here is the detailed block diagram illustrating the core architecture of an Asynchronous FIFO, alongside a timing waveform that demonstrates critical data transfer operations across independent clock domains.

1. Asynchronous FIFO Block Diagram

The block diagram shows how data moves between a faster Write Domain (100MHz) and a slightly slower, independent Read Domain (66.67MHz). The vital feature of this architecture is how the pointers cross the domains safely:

  • Dual-Port Memory: The MEM ARRAY is the central storage, written by waddr and read by raddr.
  • Cross-Domain Synchronization (Gray Code): Notice the binary-to-Gray converters. Because standard binary pointers have multiple bits switching simultaneously, they cannot safely cross asynchronous boundaries. They must be converted to Gray code (where only one bit flips at a time) before being synchronized via multi-stage flip-flops (Synchronizers) into the destination domain.
  • Flag Generation: The full flag is generated only in the Write Domain, and the empty flag is generated only in the Read Domain.

2. Example Waveform Analysis

The waveform details a sequence of operations:

  • Phase 1 (Basic Writes & Flag): We write data AAAA, BBBB, and CCCC consecutively. This triggers the write pointer to advance (0 -> 1 -> 2 -> 3). The faster write clock means these happen quickly.
  • Phase 2 (Pointer Crossover & Reads): The Gray code of wr_ptr begins crossing into the Read Domain. We then perform two consecutive reads (rd_en is high). Note the slower rd_clk period. Data AAAA is read, followed by BBBB.
  • Phase 3 (Concurrent Read/Write): Both wr_en and rd_en are active simultaneously, showing the FIFO managing bidirectional traffic on separate, uncorrelated clock domains. wr_ptr advances to 4, and rd_ptr advances to 2.

An asynchronous FIFO is used to transfer data safely between two completely independent (asynchronous) clock domains. To prevent metastability, this design converts binary pointers to Gray code before passing them across the clock domains via multi-stage synchronizers.

Asynchronous FIFO Block Diagram

An Asynchronous FIFO relies on cross-domain pointer synchronization. The write pointer is synchronized into the read clock domain to calculate the empty flag, and the read pointer is synchronized into the write clock domain to calculate the full flag.

`timescale 1ns / 1ps

module async_fifo #(
parameter int DATA_WIDTH = 8, // Data bus width
parameter int FIFO_DEPTH = 16 // FIFO Depth (Must be a power of 2)
)(
// Write Domain Interface
input logic wr_clk,
input logic wr_rst_n,
input logic wr_en,
input logic [DATA_WIDTH-1:0] wr_data,
output logic full,

// Read Domain Interface
input logic rd_clk,
input logic rd_rst_n,
input logic rd_en,
output logic [DATA_WIDTH-1:0] rd_data,
output logic empty

);

// Calculate internal address/pointer width
localparam int ADDR_WIDTH = $clog2(FIFO_DEPTH);
// Internal signals
logic [ADDR_WIDTH:0] wr_ptr_bin, wr_ptr_gray;
logic [ADDR_WIDTH:0] rd_ptr_bin, rd_ptr_gray;
logic [ADDR_WIDTH:0] wr_ptr_gray_sync, rd_ptr_gray_sync;
logic [ADDR_WIDTH-1:0] waddr, raddr;
// -------------------------------------------------------------------------
// 1. Dual-Port Memory Array (Inferred RAM)
// -------------------------------------------------------------------------
logic [DATA_WIDTH-1:0] fifo_mem [0:FIFO_DEPTH-1];
assign waddr = wr_ptr_bin[ADDR_WIDTH-1:0];
assign raddr = rd_ptr_bin[ADDR_WIDTH-1:0];
// Write operation (Write Clock Domain)
always_ff @(posedge wr_clk) begin
if (wr_en && !full) begin
fifo_mem[waddr] <= wr_data;
end
end
// Read operation (Read Clock Domain)
assign rd_data = fifo_mem[raddr];
// -------------------------------------------------------------------------
// 2. Multi-stage Flop Synchronizers (To prevent metastability)
// -------------------------------------------------------------------------
// Synchronize Write Pointer into Read Clock Domain
async_ptr_sync #(.WIDTH(ADDR_WIDTH+1)) sync_wr_to_rd (
.clk (rd_clk),
.rst_n (rd_rst_n),
.ptr_in (wr_ptr_gray),
.ptr_out (wr_ptr_gray_sync)
);
// Synchronize Read Pointer into Write Clock Domain
async_ptr_sync #(.WIDTH(ADDR_WIDTH+1)) sync_rd_to_wr (
.clk (wr_clk),
.rst_n (wr_rst_n),
.ptr_in (rd_ptr_gray),
.ptr_out (rd_ptr_gray_sync)
);
// -------------------------------------------------------------------------
// 3. Write Control Logic & Binary/Gray Counter
// -------------------------------------------------------------------------
logic [ADDR_WIDTH:0] wr_ptr_bin_next, wr_ptr_gray_next;
assign wr_ptr_bin_next = wr_ptr_bin + (wr_en && !full);
assign wr_ptr_gray_next = wr_ptr_bin_next ^ (wr_ptr_bin_next >> 1); // Binary to Gray
always_ff @(posedge wr_clk or negedge wr_rst_n) begin
if (!wr_rst_n) begin
wr_ptr_bin <= '0;
wr_ptr_gray <= '0;
end else begin
wr_ptr_bin <= wr_ptr_bin_next;
wr_ptr_gray <= wr_ptr_gray_next;
end
end
// Full Condition Logic:
// Full when MSB and MSB-1 are inverted (due to wrap around), but remaining bits match.
// example: wr_ptr_gray = 4'b1100, rd_ptr_gray_sync = 4'b0000
assign full = (wr_ptr_gray_next == {~rd_ptr_gray_sync[ADDR_WIDTH : ADDR_WIDTH-1],
rd_ptr_gray_sync[ADDR_WIDTH-2 : 0]});
// -------------------------------------------------------------------------
// 4. Read Control Logic & Binary/Gray Counter
// -------------------------------------------------------------------------
logic [ADDR_WIDTH:0] rd_ptr_bin_next, rd_ptr_gray_next;
assign rd_ptr_bin_next = rd_ptr_bin + (rd_en && !empty);
assign rd_ptr_gray_next = rd_ptr_bin_next ^ (rd_ptr_bin_next >> 1); // Binary to Gray
always_ff @(posedge rd_clk or negedge rd_rst_n) begin
if (!rd_rst_n) begin
rd_ptr_bin <= '0;
rd_ptr_gray <= '0;
end else begin
rd_ptr_bin <= rd_ptr_bin_next;
rd_ptr_gray <= rd_ptr_gray_next;
end
end
// Empty Condition Logic:
// Empty when the next read pointer perfectly matches the synchronized write pointer.
assign empty = (rd_ptr_gray_next == wr_ptr_gray_sync);

endmodule

// —————————————————————————–
// Helper Module: 2-Stage Flop Synchronizer
// —————————————————————————–
module async_ptr_sync #(
parameter int WIDTH = 5
)(
input logic clk,
input logic rst_n,
input logic [WIDTH-1:0] ptr_in,
output logic [WIDTH-1:0] ptr_out
);

logic [WIDTH-1:0] stage1_reg;
logic [WIDTH-1:0] stage2_reg;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
stage1_reg <= '0;
stage2_reg <= '0;
end else begin
stage1_reg <= ptr_in;
stage2_reg <= stage1_reg;
end
end
assign ptr_out = stage2_reg;

endmodule

Architectural Design Highlights

  • Gray Code Optimization: Passing standard binary pointers (011 $\rightarrow$ 100) across clock domains creates hazardous transitions where multiple bits switch at once. If sampled mid-cycle, the synchronizer will capture corrupt states. Gray code changes by only one bit at a time, neutralizing multi-bit synchronization hazards.
  • Asynchronous Flags: The flags are evaluated in their respective destination domains to prevent starvation or overrun.
    • full is generated purely on wr_clk using the synchronized rd_ptr.
    • empty is generated purely on rd_clk using the synchronized wr_ptr.
  • Pessimistic Flag Boundaries: Because it takes 2 clock cycles for pointers to cross over the synchronizer network, the flags are inherently “pessimistic” but completely safe. The FIFO might report full for a couple of cycles longer than it actually is, or empty for slightly longer, but it will never underflow or overflow data.

Here is a comprehensive self-checking SystemVerilog testbench to verify the asynchronous FIFO. It instantiates the FIFO with independent write and read clock domains, generates random data patterns, and uses a scoreboard-style array to verify that the data read matches the data written in a strict First-In, First-Out order.

`timescale 1ns / 1ps

module tb_async_fifo;

// --- Parameters ---
localparam int W = 8; // Data Width
localparam int D = 16; // FIFO Depth
// --- Write Domain Signals ---
logic wr_clk;
logic wr_rst_n;
logic wr_en;
logic [W-1:0] wr_data;
logic full;
// --- Read Domain Signals ---
logic rd_clk;
logic rd_rst_n;
logic rd_en;
logic [W-1:0] rd_data;
logic empty;
// --- Testbench Internal Verification Queue ---
// Acts as our behavioral reference model (Golden Model)
logic [W-1:0] expected_queue [$];
logic [W-1:0] popped_data;
int match_count = 0;
int error_count = 0;
// --- Device Under Test (DUT) Instantiation ---
async_fifo #(
.DATA_WIDTH(W),
.FIFO_DEPTH(D)
) dut (
.wr_clk (wr_clk),
.wr_rst_n (wr_rst_n),
.wr_en (wr_en),
.wr_data (wr_data),
.full (full),
.rd_clk (rd_clk),
.rd_rst_n (rd_rst_n),
.rd_en (rd_en),
.rd_data (rd_data),
.empty (empty)
);
// --- Clock Generators ---
// Write Clock: 100 MHz (10ns period)
always #5 wr_clk = ~wr_clk;
// Read Clock: 66.67 MHz (~15ns period) - Completely asynchronous
always #7.5 rd_clk = ~rd_clk;
// --- Self-Checking Monitor (Read Domain) ---
always @(posedge rd_clk) begin
if (rd_en && !empty) begin
// Sample data on the next clock cycle when it is valid from the RAM
#1;
if (expected_queue.size() > 0) begin
popped_data = expected_queue.pop_front();
if (rd_data === popped_data) begin
$display("[MONITOR] %t | MATCH: Data Read = 0x%h", $time, rd_data);
match_count++;
end else begin
$error("[MONITOR] %t | ERROR: Mismatch! Expected 0x%h, Got 0x%h", $time, popped_data, rd_data);
error_count++;
end
end else begin
$error("[MONITOR] %t | ERROR: FIFO Read occurred but expected queue is empty!", $time);
error_count++;
end
end
end
// --- Stimulus Generation ---
initial begin
// Initialize Signals
wr_clk = 0;
rd_clk = 0;
wr_rst_n = 0;
rd_rst_n = 0;
wr_en = 0;
rd_en = 0;
wr_data = 0;
// Apply Synchronous/Asynchronous Reset
#30;
wr_rst_n = 1;
rd_rst_n = 1;
$display("[TB INTI] %t | Resets released. Starting Test...", $time);
#20;
// --- Test Case 1: Sequential Writes and Reads ---
$display("\n--- TEST CASE 1: Basic Write and Read ---");
repeat (5) begin
@(posedge wr_clk);
if (!full) begin
wr_en = 1;
wr_data = $urandom_range(8'h00, 8'hFF);
expected_queue.push_back(wr_data);
$display("[WRITE] %t | Writing: 0x%h", $time, wr_data);
end
end
@(posedge wr_clk);
wr_en = 0;
// Wait a few read clock cycles for synchronization to catch up
repeat (3) @(posedge rd_clk);
repeat (5) begin
@(posedge rd_clk);
if (!empty) begin
rd_en = 1;
end
end
@(posedge rd_clk);
rd_en = 0;
// --- Test Case 2: Fill to Full Capacity ---
$display("\n--- TEST CASE 2: Filling FIFO to Capacity (Full Check) ---");
while (!full) begin
@(posedge wr_clk);
if (!full) begin
wr_en = 1;
wr_data = $urandom_range(8'h00, 8'hFF);
expected_queue.push_back(wr_data);
$display("[WRITE] %t | Writing: 0x%h", $time, wr_data);
end
end
@(posedge wr_clk);
wr_en = 0;
$display("[STATUS] %t | FIFO identified as FULL.", $time);
// Try an extra malicious write while full to verify safe guard logic
@(posedge wr_clk);
wr_en = 1; wr_data = 8'hFF;
@(posedge wr_clk);
wr_en = 0;
// --- Test Case 3: Empty to Zero Capacity ---
$display("\n--- TEST CASE 3: Emptying FIFO Completely (Empty Check) ---");
while (!empty) begin
@(posedge rd_clk);
if (!empty) begin
rd_en = 1;
end
end
@(posedge rd_clk);
rd_en = 0;
$display("[STATUS] %t | FIFO identified as EMPTY.", $time);
// --- Test Case 4: Simultaneous Write and Read (Back-to-Back) ---
$display("\n--- TEST CASE 4: Concurrent Read and Write ---");
fork
// Write Thread
begin
repeat (20) begin
@(posedge wr_clk);
if (!full) begin
wr_en = $urandom_range(0, 1); // Randomize active writing
wr_data = $urandom_range(8'h00, 8'hFF);
if (wr_en) begin
expected_queue.push_back(wr_data);
$display("[CONCURRENT WRITE] %t | Data: 0x%h", $time, wr_data);
end
end else begin
wr_en = 0;
end
end
@(posedge wr_clk);
wr_en = 0;
end
// Read Thread
begin
repeat (30) begin
@(posedge rd_clk);
if (!empty) begin
rd_en = $urandom_range(0, 1); // Randomize active reading
end else begin
rd_en = 0;
end
end
@(posedge rd_clk);
rd_en = 0;
end
join
// Final clean out sweep
#100;
$display("\n--- FINAL CLEANUP: Flushing remaining data ---");
while (!empty) begin
@(posedge rd_clk);
rd_en = 1;
end
@(posedge rd_clk);
rd_en = 0;
// --- Final Report ---
#50;
$display("\n=============================================");
$display(" SIMULATION REPORT ");
$display("=============================================");
$display(" Total Successful Matches : %d", match_count);
$display(" Total Error Mismatches : %d", error_count);
if (error_count == 0 && match_count > 0) begin
$display(" STATUS: TEST PASSED SUCCESSFULLY ");
end else begin
$display(" STATUS: TEST FAILED ");
end
$display("=============================================");
$finish;
end

endmodule

Verification Strategy Details

  • Asynchronous Clock Architecture: The simulation establishes an arbitrary $\text{100 MHz}$ write domain and a completely unaligned $\text{66.67 MHz}$ read domain to mimic hardware clock jitter and cross-domain phase variations.
  • Scoreboard Modeling: The verification environment uses a SystemVerilog unbounded dynamic queue (expected_queue [$]). When data is written to the physical DUT, it’s also pushed to the back of the queue. When data is verified on output, it’s checked against the front of the queue.
  • Boundary Validation: Test Cases 2 and 3 intentionally target the corner states (full and empty) to ensure pointer overflow prevents hardware faults or data overwrites.
  • Concurrent Assertions Simulation (fork-join): Test Case 4 triggers parallel execution, stressing the Gray code synchronizers by modifying pointers across both interfaces simultaneously.

To ensure that the Asynchronous FIFO functions reliably under all circumstances, we can embed SystemVerilog Assertions (SVA) directly inside the module or bind them externally.

However, looking closely at your requirement: “fifo full and fifo empty can never occur” is a critical rule to clarify. In a functional FIFO, reaching a full or empty state is completely normal and expected behavior when the data rates don’t match.

What must never happen are the catastrophic failures associated with those states:

  1. Overflow: Writing data when the FIFO is already full (corrupting data).
  2. Underflow: Reading data when the FIFO is already empty (reading garbage data).
  3. Illegal State: The FIFO stating it is simultaneously full and empty (unless the FIFO depth is 0, which is physically impossible here).

Here is the SystemVerilog code containing the formal assertions to catch these exact violation conditions.

SystemVerilog Assertion Code Block

You can place this code block directly at the bottom of your async_fifo module (before endmodule) or inside your testbench. These can also be reused for the synchronous FIFO testing.

// =========================================================================
// SYSTEMVERILOG ASSERTIONS (SVA) FOR SAFETY AND INTEGRITY
// =========================================================================

// 1. Mutex Check: FIFO cannot be Full and Empty at the same time
assert_full_and_empty_mutex: assert property (
@(posedge wr_clk) disable iff (!wr_rst_n)
!(full && empty)
) else $error(“[SVA ERROR] FIFO is simultaneously FULL and EMPTY! Hardware state is corrupted.”);

// 2. Overflow Check: If FIFO is full, a write enable (wr_en) must NOT occur
assert_no_overflow: assert property (
@(posedge wr_clk) disable iff (!wr_rst_n)
full |-> !wr_en
) else $error(“[SVA ERROR] FIFO Overflow Detected! Write asserted while FIFO is full.”);

// 3. Underflow Check: If FIFO is empty, a read enable (rd_en) must NOT occur
assert_no_underflow: assert property (
@(posedge rd_clk) disable iff (!rd_rst_n)
empty |-> !rd_en
) else $error(“[SVA ERROR] FIFO Underflow Detected! Read asserted while FIFO is empty.”);

// =========================================================================
// OPTIONAL: COVERAGE PROPERTIES (To ensure your testbench actually hits corner cases)
// =========================================================================
cover_fifo_full: cover property (@(posedge wr_clk) disable iff (!wr_rst_n) full);
cover_fifo_empty: cover property (@(posedge rd_clk) disable iff (!rd_rst_n) empty);

How to Read the SVA Syntax

  • disable iff (!wr_rst_n): This ensures that assertions are turned off while the system is resetting. Checking for overflow/underflow during initialization leads to false positives.
  • |-> (Implication Operator): This means “If the left side is true, the right side must be true at that exact same clock cycle.” * For example, full |-> !wr_en translates to: If full is active, then wr_en must be evaluated as 0.

Behavioral Verification Matrix

When you run your testbench with an EDA tool (like Questa, VCS, or Riviera-PRO) with assertions enabled, the simulator will continuously track these properties:

Evaluated PropertyClock DomainExpected StatusIf Violated…
!(full && empty)wr_clkAlways TrueSimulation flags a critical state error.
`full-> !wr_en`wr_clkTrue
`empty-> !rd_en`rd_clkTrue

IEEE floating point addition example

to be added

IEEE 754 floating point comparator example

/*

  • Do not change Module name
    */
    // IEEE 754 binary32 : one bit sign, 8 bit exponent, 23 bit fraction bits
    // David Fong

module fp_compare (
input [31:0] float_a, // float A
input [31:0] float_b, // float B
output reg greater_than, // A > B
output reg less_than, // A < B output

output reg equal); // A = B;

reg [31:31] sign_a, sign_b;
reg [30:23] exp_a, exp_b;
reg [22:0] mant_a, mant_b;

// systemverilog
//always @* begin

// use regular verilog
always @ (float_a, float_b) begin

// systemverilog
/*
reg [31:31] sign_a, sign_b;
reg [30:23] exp_a, exp_b;
reg [22:0] mant_a, mant_b;
*/

sign_a = float_a[31];
sign_b = float_b[31];
exp_a = float_a[30:23];
exp_b = float_b[30:23];
mant_a = float_a[22:0];
mant_b = float_b[22:0];

// compare signs
if (sign_a < sign_b) begin // 0 means pos, 1 means neg greater_than = 1; less_than = 0; equal = 0; end else if (sign_a > sign_b) begin
greater_than = 0;
less_than = 1;
equal = 0;
end else begin
// compare exponent assuming unsigned 0 to 255
if (exp_a > exp_b) begin
greater_than = 1;
less_than = 0;
equal = 0;
end else if (exp_a < exp_b) begin greater_than = 0; less_than = 1; equal = 0; end else begin // compare mantissa portion if (mant_a > mant_b) begin
greater_than = 1;
less_than = 0;
equal = 0;
end else if (mant_a < mant_b) begin
greater_than = 0;
less_than = 1;
equal = 0;
end else begin // last begin
// Numbers are equal
greater_than = 0;
less_than = 0;
equal = 1;
end // matching last end
end
end

end // matching always begin

endmodule

module main;

  reg [31:0] ia ;
  reg [31:0] ib ;
  wire gt,lt,eq;
  fp_compare ifp_compare(ia, ib, gt, lt, eq);

initial

begin
  $display("Hello, World");
  ia = 32'h0;
  ib = 32'h0;
  #10;
  $display("=========================");
  $display("EXERCISE SIGN BIT TESTING");
  $display("%t : ia and ib = 0 so expect eq=1", $time);
  $display("%t : ia = %h, ib = %h, gt = %b, lt = %b, eq = %b", $time, ia, ib, gt, lt, eq); 
  ia = 32'h80000000;
  ib = 32'h0;
  #10;
  $display("%t : ia is negative fp number, so expect lt=1", $time);
  $display("%t : ia = %h, ib = %h, gt = %b, lt = %b, eq = %b", $time, ia, ib, gt, lt, eq);
  ib = 32'h80000000;
  ia = 32'h0;
  #10;
  $display("%t : ib is negative fp number, so expect gt=1, ", $time);
  $display("%t : ia = %h, ib = %h, gt = %b, lt = %b, eq = %b", $time, ia, ib, gt, lt, eq);
  #10;
  $display("=========================");
  $display("EXERCISE EXPONENT BIT TESTING");
  ia = 32'h70000000;
  ib = 32'h0;
  #10;
  $display("%t : ia has an exponent bit, so expect gt=1, ", $time);
  $display("%t : ia = %h, ib = %h, gt = %b, lt = %b, eq = %b", $time, ia, ib, gt, lt, eq);      
  ia = 32'h70000000;
  ib = 32'h71000000;
  #10;
  $display("%t : ib has larger exponent bit value, so expect lt=1, ", $time);
  $display("%t : ia = %h, ib = %h, gt = %b, lt = %b, eq = %b", $time, ia, ib, gt, lt, eq);
  ia = 32'h71000000;
  ib = 32'h71000000;
  #10;
  $display("%t : ia and ib has same exponent bit value, so expect eq=1, ", $time);
  $display("%t : ia = %h, ib = %h, gt = %b, lt = %b, eq = %b", $time, ia, ib, gt, lt, eq); 
  $display("=========================");
  $display("EXERCISE 23 BIT SIGNFICANT TESTING");
  ia = 32'h007FFFFF; // all 24 bits are 1's
  ib = 32'h007FFFFF;
  #10;
  $display("%t : ia = ib 23 bits, so expect eq=1, ", $time);
  $display("%t : ia = %h, ib = %h, gt = %b, lt = %b, eq = %b", $time, ia, ib, gt, lt, eq);      
  ia = 32'h007FFFF0;
  ib = 32'h007FFFFF;
  #10;
  $display("%t : ib has larger bit value, so expect lt=1, ", $time);
  $display("%t : ia = %h, ib = %h, gt = %b, lt = %b, eq = %b", $time, ia, ib, gt, lt, eq);
  ia = 32'h71000000;
  ib = 32'h71000000;
  #10;
  $display("%t : ia has larger bit value, so expect eq=1, ", $time);
  $display("%t : ia = %h, ib = %h, gt = %b, lt = %b, eq = %b", $time, ia, ib, gt, lt, eq);      
  $finish ;
end

endmodule

Simulation results for IEEE 754 floating point comparator : greater_than, less_than, equal

Hello, World

=========================

EXERCISE SIGN BIT TESTING

10 : ia and ib = 0 so expect eq=1

                  10 : ia = 00000000, ib = 00000000, gt = 0, lt = 0, eq = 1

                  20 : ia is negative fp number, so expect lt=1

                  20 : ia = 80000000, ib = 00000000, gt = 0, lt = 1, eq = 0

                  30 : ib is negative fp number, so expect gt=1,

                  30 : ia = 00000000, ib = 80000000, gt = 1, lt = 0, eq = 0

=========================

EXERCISE EXPONENT BIT TESTING

                  50 : ia has an exponent bit, so expect gt=1,

                  50 : ia = 70000000, ib = 00000000, gt = 1, lt = 0, eq = 0

                  60 : ib has larger exponent bit value, so expect lt=1,

60 : ia = 70000000, ib = 71000000, gt = 0, lt = 1, eq = 0

                  70 : ia and ib has same exponent bit value, so expect eq=1,

                  70 : ia = 71000000, ib = 71000000, gt = 0, lt = 0, eq = 1

=========================

EXERCISE 23 BIT SIGNFICANT TESTING

                  80 : ia = ib 23 bits, so expect eq=1,

                  80 : ia = 007fffff, ib = 007fffff, gt = 0, lt = 0, eq = 1

                  90 : ib has larger bit value, so expect lt=1,

                  90 : ia = 007ffff0, ib = 007fffff, gt = 0, lt = 1, eq = 0

                 100 : ia has larger bit value, so expect eq=1,

                 100 : ia = 71000000, ib = 71000000, gt = 0, lt = 0, eq = 1

main.v:146: $finish called at 100 (1s)

Rate Adapter or Upsizer Example (convert 32 bit to 256 bit using valid/ready handshake)

Below is the complete, synthesizable SystemVerilog code for a Data Rate Adapter (sometimes called a data width converter or funnel/upsizer).

This module accumulates eight consecutive 32-bit words from a master to form a single 256-bit word, which is then passed to a downstream slave using a standard Valid/Ready handshake mechanism.

`timescale 1ns / 1ps

module rate_adapter_32_to_256 (
input logic clk, // System Clock
input logic rst_n, // Active-low synchronous reset

// Upstream Master Interface (32-bit Input)
input logic [31:0] s_axis_tdata, // Input data stream
input logic s_axis_tvalid, // Master data valid
output logic s_axis_tready, // Slave ready to accept
// Downstream Slave Interface (256-bit Output)
output logic [255:0] m_axis_tdata, // Upsized output data
output logic m_axis_tvalid, // Master data valid to downstream slave
input logic m_axis_tready // Downstream slave ready to accept

);

// --- Internal Signals & Registers ---
logic [7:0][31:0] data_buffer; // Packed array to accumulate 8 words
logic [2:0] word_count; // Track current index (0 to 7)
logic out_valid_reg; // Internal pipeline register for output valid
// --- Combinational Handshake Logic ---
// The master interface is ready if our buffer is not full,
// OR if it is full but the downstream slave is clearing it this cycle.
assign s_axis_tready = !out_valid_reg || m_axis_tready;
// Output assignment mapping the packed internal array to the continuous 256-bit bus
assign m_axis_tdata = data_buffer;
assign m_axis_tvalid = out_valid_reg;
// --- Control and Data Accumulation Path ---
always_ff @(posedge clk) begin
if (!rst_n) begin
word_count <= '0;
out_valid_reg <= 1'b0;
data_buffer <= '0;
end else begin
// 1. Downstream Handshake Management
// If downstream slave accepts the 256-bit packet, clear the valid flag
if (m_axis_tvalid && m_axis_tready) begin
out_valid_reg <= 1'b0;
end
// 2. Upstream Data Accumulation
// Accept input when master provides valid data and we are ready
if (s_axis_tvalid && s_axis_tready) begin
// Store the incoming 32-bit word into the corresponding slot
data_buffer[word_count] <= s_axis_tdata;
// Increment word counter. It naturally wraps around from 7 to 0 (3-bit counter)
word_count <= word_count + 1'b1;
// When reaching the 8th word (index 3'b111), register the output as valid
if (word_count == 3'b111) begin
out_valid_reg <= 1'b1;
end
end
end
end

endmodule

Architectural & Handshake Design Highlights

  • Backpressure Support: The s_axis_tready signal ensures that the upstream master pauses if the downstream network stalls. If the 256-bit output buffer is full (out_valid_reg == 1), it will withhold s_axis_tready until m_axis_tready goes high to empty the register.
  • Array Index Mapping: The buffer uses a packed multi-dimensional array logic [7:0][31:0] data_buffer. This simplifies indexing (data_buffer[word_count]), which synthesis tools automatically map to standard multiplexer logic.
  • Zero-Bubble Cycling: When the 8th word arrives simultaneously with a downstream read request (m_axis_tready), the module immediately transitions to accumulating the next packet without inserting dead clock cycles.

Example Verification Testbench

This testbench feeds sequential data to verify accumulation boundaries and introduces random downstream ready drops to test handshaking robustness.

module tb_rate_adapter;

logic clk;
logic rst_n;
logic [31:0] s_axis_tdata;
logic s_axis_tvalid;
logic s_axis_tready;
logic [255:0] m_axis_tdata;
logic m_axis_tvalid;
logic m_axis_tready;
// Unit Under Test
rate_adapter_32_to_256 uut (.*);
// 100MHz Clock
always #5 clk = ~clk;
initial begin
clk = 0;
rst_n = 0;
s_axis_tvalid = 0;
s_axis_tdata = 0;
m_axis_tready = 1; // Slave ready initially
#20 rst_n = 1;
@(posedge clk);
// --- Stimulus 1: Smooth 8-word burst transmission ---
$display("[TB] Sending 8 consecutive words...");
for (int i = 1; i <= 8; i++) begin
s_axis_tvalid = 1;
s_axis_tdata = i; // Data = 0x1, 0x2, ... 0x8
do begin
@(posedge clk);
end while (!s_axis_tready); // Handle wait states if any
end
s_axis_tvalid = 0;
// Monitor outputs
@(posedge clk);
if (m_axis_tvalid) begin
$display("[TB SUCCESS] 256-bit packet ready: %h", m_axis_tdata);
end
// --- Stimulus 2: Downstream Slave Backpressure ---
$display("[TB] Testing slave backpressure...");
m_axis_tready = 0; // Downstream block simulates stall
// Feed next 8 packets
for (int i = 9; i <= 16; i++) begin
s_axis_tvalid = 1;
s_axis_tdata = i;
do begin
@(posedge clk);
end while (!s_axis_tready);
end
s_axis_tvalid = 0;
#20;
$display("[TB] Releasing slave stall.");
m_axis_tready = 1; // Free the bus
@(posedge clk);
#10;
$finish;
end

endmodule

IoT : Internet of Things and Market Segmentation

August 6, 2015

As a television ad once promoted, we live in a modern day society where we want to :

Share this. Share that.

How can we do that?

The current set of millions of smartphones (iPhones) can easily share voice and text conversations and photos.

But what’s next to do.

We build some intelligence into devices to allow remote monitoring and other sensing applications so that we can share even more information related to devices like washing machines, refrigerators, lights, sprinklers, home security, and even smart watches to monitor health conditions in real-time.

This growing network of interconnected devices will expand the internet bandwidth requirements.

This new market for intelligent internet of things (IoT) is generally segmented into these categories with the top two already existing and the remainder needing to be built or already in design and production.  These are ordered in somewhat reducing complexity:

  • Servers / Routers
  • Smartphones / Tablets / Home PCs / Laptops
  • Wearable infotainment
  • Wearable Fitness and Health
  • Smart Home
  • Smart Appliance
  • Safety and Security
  • Smart City / Metering
  • Commerce

Reference Synopsys analysis of IP components needed to build these IoT devices

https://www.synopsys.com/IP/market-segments/iot/Pages/default.aspx

HIGH END SOC FOR IOT

The high-end Internet of Things are occupied by the Smartphones/Tablets/Home PCs/Laptop and an example SOC diagram would like below with CPU, display, LDDR and sensors.  These usually use the latest technology process nodes like 10nm FinFET to make the billion transistor devices economical and probably cost between $20 to $100 per device.

HIGH_END_SOC_BLOCK_DIAGRAM

LOW-END SOC FOR IOT

The low-end SOC for IoT has significantly reduced IP but maintains bare functional and communication capabilities such as as a CPU, bluetooth and sensor.  These would probably go into smart metering devices and use 28nm process node technology and cost around $5 to $20.

LOW_END_SOC_BLOCK_DIAGRAM

BOTTOM LOW-END SOC FOR IOT

The bottom SOC for IoT can be a bare minimum of CPU, some local storage RAM and sensor.  Cost should be within a $1 or less to be economically feasible and produced in the billions.  It can probably use older process nodes like 90nm and cost less than $1 to manufacture. These could be added as tracking devices for very expensive items shipped across international borders.

BOTTOM_LOW_END_SOC_BLOCK_DIAGRAM

UVM Tutorial 3: Systemverilog Testbench Principles

March 6, 2015

Design a site like this with WordPress.com
Get started