SYSTEMVERILOG SERIES · SV-06

SystemVerilog Series — SV-06: Attributes — VLSI Trainers
SystemVerilog Series · SV-06

Attributes

How to attach named properties to any SystemVerilog construct using the (* *) syntax — what the default type rule means, where attributes can appear, and the real-world synthesis and lint directives every RTL engineer encounters.

🏷 What Are Attributes?

An attribute is a named annotation attached to a Verilog or SystemVerilog construct. Attributes pass information to tools — synthesis engines, static analysers, linters, formal tools — without changing the simulation semantics of the design. The simulator completely ignores attributes; synthesis and lint tools read them to modify their behaviour.

Attributes were introduced in Verilog-2001. SystemVerilog extends them in two ways:

  • Attributes can now be placed on all new SystemVerilog constructs (interfaces, clocking blocks, assertions, classes, etc.).
  • SystemVerilog defines a default data type for attributes that have no explicit value.
Attributes vs compiler directives: Compiler directives (like `timescale, `define, `ifdef) affect how the source is parsed and are processed before compilation. Attributes are part of the compiled source — they are associated with specific language constructs and are accessible to tools via the Programming Language Interface (PLI/VPI). They have no effect on simulation behaviour.

📄 Syntax

An attribute instance uses (* and *) delimiters. Inside, you can list one or more comma-separated attribute specifications. Each attribute has a name and an optional value expression.

Attribute syntax
// General form
(* attr_name *)                        // no value — type is bit, value = 1
(* attr_name = constant_expression *)  // with value — type from expression
(* attr1, attr2 = expr, attr3 *)       // multiple attributes in one instance

// The delimiters are (* and *) — note: not # or [ ]

Minimal examples

// Attribute on a module
(* synthesis, full_case *)
module my_module (...);

// Attribute on a signal declaration
(* keep *) logic [7:0] debug_bus;

// Attribute with an integer value on a module instance
(* LOC = "SLICE_X10Y20" *)
my_counter u_cnt (.clk, .rst, .count);

// Attribute on a statement
always_comb begin
  (* parallel_case *)
  case(state)
    IDLE:  next = FETCH;
    FETCH: next = EXEC;
    EXEC:  next = DONE;
    DONE:  next = IDLE;
  endcase
end

📌 Default Attribute Type

This is the only new rule SystemVerilog adds to attributes versus Verilog-2001. The rule is simple:

Default type rule

If an attribute has no value expression: its type is bit and its value is 1.
If an attribute has a value expression: its type is the type of that expression.

// No value — type is bit, value = 1
(* keep *)         // keep : bit = 1
(* synthesis *)    // synthesis : bit = 1
(* full_case *)    // full_case : bit = 1

// With value — type inferred from expression
(* max_fanout = 8 *)           // integer — type is int
(* LOC = "SLICE_X10Y20" *)    // string literal — type is string
(* effort = 2.5 *)             // real — type is real
(* width = 4'hF *)             // sized literal — type is bit[3:0]
Why this matters: Tools that read attributes via the VPI (Verilog Programming Interface) inspect both the attribute name and its value type. Before SystemVerilog, an attribute with no value had an undefined type — some tools treated it as 0 (integer), others as an empty string. The bit = 1 default makes the semantics unambiguous and consistent across tools.

🔑 Type & Value Rules

The expression in an attribute value must be a constant expression — no variables, no runtime values. The type of the attribute is determined entirely by the expression.

Attribute syntax Resulting type Resulting value Notes
(* keep *) bit 1 No value → SV default: bit = 1
(* max_fanout = 8 *) int (unsized integer) 8 Unsized decimal literal → int
(* width = 4’hF *) bit [3:0] 4’hF Sized literal → matching packed type
(* name = “uart” *) string “uart” String literal → string type
(* effort = 2.5 *) real 2.5 Real literal → real type
(* freq = 100_000_000 *) int 100000000 Underscore separator ignored
(* async = 1’b0 *) bit 0 1-bit literal → bit type, value 0
Attribute values must be constant expressions. You cannot use variables, function calls, or runtime expressions. Tools evaluate attribute values at elaboration time. The following would be illegal: (* depth = fifo_depth *) if fifo_depth is a module port or signal — it must be a parameter, localparam, or literal to be legal.

📌 Where Attributes Can Appear

In Verilog-2001, attributes could be placed on modules, instances, nets, variables, statements, and operators. SystemVerilog extends this to cover all new language constructs. An attribute always appears immediately before the construct it annotates.

Module / Interface / Program
Before the module, interface, or program keyword. Annotates the entire design unit.
Port declarations
Before the port direction keyword (input, output, inout, ref). Annotates that port.
Net & variable declarations
Before the type keyword. Annotates the declared signal or variable.
Module instances
Before the module type name of the instantiation. Annotates that specific instance.
Statements
Before any procedural statement: if, case, for, assign, etc.
Expressions / operators
Between the operator and its operand. Annotates how the tool should implement that operator.
Tasks & functions
Before the task or function keyword. Annotates the subroutine.
Packages
Before the package keyword. Annotates the package.
Clocking blocks
Before the clocking keyword. New SV construct fully supported.

New SystemVerilog Placements

Every construct added by SystemVerilog supports attributes. Here are examples on the most commonly encountered new constructs.

// Interface declaration
(* synthesis, bus_type = "AXI4" *)
interface axi_if(input logic clk);
  logic [31:0] addr, data;
endinterface

// always_ff block
(* dont_touch *)
always_ff @(posedge clk) q <= d;

// Clocking block
(* synthesis_clock_group *)
clocking cb @(posedge clk);
  input  #1 data_in;
  output #2 data_out;
endclocking

// Assertion (concurrent)
(* checker = "req_ack_protocol" *)
REQ_ACK: assert property (@(posedge clk) req |-> ##[1:4] ack);

// Package
(* library = "vlsi_trainers_stdlib" *)
package my_pkg;
  typedef logic[31:0] word_t;
endpackage

// Class definition
(* uvm_component *)
class my_driver extends uvm_driver;
endclass

// Operator attribute: tell synthesis to use carry-chain adder
logic [7:0] sum = a (* use_carry_chain *) + b;

// Conditional expression attribute
logic y = sel ? (* mux_type = "lut" *) a : b;

Real-World Synthesis & Lint Directives

In practice you will encounter attributes through synthesis and lint tool directives. The following are the most common across Synopsys Design Compiler, Vivado, and common lint tools — all expressed using the (* *) attribute syntax.

Synthesis: controlling logic optimisation

// Keep this signal — prevent synthesis from removing/merging it
// Useful for debug probes and testability signals
(* keep *) logic debug_valid;
(* keep = "true" *) logic debug_count;   // Vivado string form

// Don't touch this register — no retiming, no merging
(* dont_touch = "yes" *) logic [7:0] pipeline_reg;

// Maximum fanout constraint
(* max_fanout = 32 *) logic fast_clock_en;

// Mark a signal as a clock for CDC analysis
(* clock *) logic sys_clk;

// Register balancing / retiming hint
(* register_balancing = "yes" *)
module pipelined_mac (...);

Case statement: full_case and parallel_case

typedef enum logic [1:0] {IDLE, FETCH, EXEC, DONE} state_t;
state_t state;

always_comb begin
  // full_case:     all possible values are covered — no latches inferred
  // parallel_case: all branches are mutually exclusive — priority encoder
  //                not inferred, simpler mux generated
  (* full_case, parallel_case *)
  case(state)
    IDLE:  next_state = FETCH;
    FETCH: next_state = EXEC;
    EXEC:  next_state = DONE;
    DONE:  next_state = IDLE;
  endcase
end
prefer unique case or priority case over attributes for new code. SystemVerilog added unique case (mutually exclusive, no default needed) and priority case (first-match evaluation) as first-class keywords. These communicate intent to both synthesis and simulation, and generate warnings if the condition is violated at runtime. The full_case/parallel_case attributes only affect synthesis — the simulator ignores them — which can lead to simulation/synthesis mismatches. For new SV code, prefer the keywords.

Vivado FPGA attributes

// Pin location constraint (equivalent to XDC constraint, inline)
(* LOC = "RAMB36_X2Y5" *)
my_bram u_ram (.clka(clk), .wea(we), .addra(addr), .dina(din), .douta(dout));

// Mark an input as an async reset for proper timing exception
(* ASYNC_REG = "TRUE" *)
logic [1:0] rst_sync;

// Prevent IO buffer insertion (useful when driving between FPGAs)
(* IOB = "FALSE" *)
output logic direct_out;

Lint and formal tool annotations

// Mark a potential incomplete sensitivity list as intentional
// (some lint tools check this)
(* lint_off = "IMPLICIT" *)
always @(a or b)   // c deliberately omitted from sensitivity
  y = a & b;

// Mark a module as a blackbox for formal tools
(* blackbox *)
module external_ip (
  input  logic clk, rst,
  output logic ready
);

📋 Multiple Attributes & Multiple Instances

Multiple attributes can be combined in a single (* *) block (comma-separated), or spread across multiple (* *) blocks on the same construct. Both forms are equivalent.

// Single attribute instance with multiple attributes
(* keep, max_fanout = 8, dont_touch *)
logic clk_en;

// Equivalent: multiple separate attribute instances
(* keep *)
(* max_fanout = 8 *)
(* dont_touch *)
logic clk_en;

// Attributes on multiple items in a module
(* synthesis, top_module *)
module top (
  (* clock *)          input  logic        clk,
  (* async_reset *)    input  logic        rst_n,
                        input  logic [7:0]  data_in,
  (* keep *)           output logic [7:0]  data_out
);
  (* max_fanout = 16 *)
  logic internal_en;

  (* dont_touch *)
  always_ff @(posedge clk or negedge rst_n) begin
    if(!rst_n) data_out <= '0;
    else       data_out <= data_in;
  end
endmodule

📋 Quick Reference

Syntax forms at a glance

(* name *)                // type: bit, value: 1  (SV default)
(* name = expr *)         // type: type of expr, value: eval(expr)
(* a, b = 5, c *)        // three attributes in one block
(* a *) (* b = 5 *)      // same as above — two separate blocks

Rules to remember

  • Attribute values must be constant expressions — no runtime variables or function calls.
  • An attribute with no value has type bit and value 1 (the SV default type rule).
  • Attribute type follows the value expression’s type — sized literal gives a packed type, string gives string, real gives real.
  • Attributes have no effect on simulation — only tools (synthesis, lint, formal, PLI) read them.
  • Attributes appear on all Verilog-2001 constructs plus all new SystemVerilog constructs (interfaces, clocking blocks, assertions, classes, packages, programs).
  • Multiple (* *) instances on the same construct are equivalent to a single block with comma-separated names.
  • For case-statement control in new SV code, prefer unique case / priority case keywords over full_case/parallel_case attributes.

Common attribute names by tool

AttributeToolWhat it does
keep Synopsys DC, Vivado Preserve signal — do not remove or merge during optimisation
dont_touch Synopsys DC, Vivado Do not retime, replicate, or restructure this logic
max_fanout = N Synopsys DC Limit the fanout of this signal to N loads (tool will buffer)
full_case Synopsys DC, Cadence All case values covered — synthesis skips default latch prevention
parallel_case Synopsys DC, Cadence Case branches are mutually exclusive — generate simple mux not priority encoder
ASYNC_REG = “TRUE” Vivado Mark register as asynchronous (for timing exception handling in CDC paths)
LOC = “…” Vivado Assign this instance to a specific FPGA resource location
IOB = “FALSE” Vivado Prevent automatic IO buffer inference on this port
clock Various lint / CDC tools Declare this signal as a clock for clock-domain crossing analysis
blackbox Formal / synthesis Treat this module as an opaque black box — do not look inside
Coming next: The next article covers Operators & Expressions — the new assignment operators (+=, -=, ++, --), wild equality (=?=, !?=), operator precedence, and the inside set-membership operator.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top