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.
`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.
// 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:
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]
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 |
(* 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, or program keyword. Annotates the entire design unit.input, output, inout, ref). Annotates that port.if, case, for, assign, etc.task or function keyword. Annotates the subroutine.package keyword. Annotates the package.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
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
bitand value1(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 casekeywords overfull_case/parallel_caseattributes.
Common attribute names by tool
| Attribute | Tool | What 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 |
+=, -=, ++, --), wild equality (=?=, !?=), operator precedence, and the inside set-membership operator.
