Cheat sheet

VHDL, Verilog and SystemVerilog side by side

A Rosetta table of the constructs you write every day in VHDL-2008, Verilog-2001 and SystemVerilog, with compiled snippets for the ones a table cannot hold.

Last verified 13 Nov 2026· VHDL-2008, Verilog-2001, SystemVerilog-2012, checked with Icarus Verilog 13.0 and nvc

This page is for engineers who read one hardware description language and need to write another. It maps the synthesisable subset only. Every snippet below was compiled, as part of a complete module, with Icarus Verilog (SystemVerilog mode) or nvc (VHDL-2008) before publication. Where SystemVerilog and Verilog-2001 differ, both are shown, because many vendor flows still default to Verilog-2001 for .v files and SystemVerilog for .sv.

Rosetta table

Construct VHDL-2008 Verilog-2001 SystemVerilog
Design unit entity plus architecture module module
Input port a : in std_logic input wire a input logic a
Output port y : out std_logic_vector(7 downto 0) output reg [7:0] y or output wire output logic [7:0] y
Net or variable signal (in architecture), variable (in process) wire (driven by assign), reg (driven by always) logic for both
Register, sync reset if rising_edge(clk) then if rst = '1' then ... elsif en = '1' then ... always @(posedge clk) if (rst) ... else if (en) ... always_ff @(posedge clk) ...
Async reset if rst = '1' then ... elsif rising_edge(clk) then ... always @(posedge clk or posedge rst) always_ff @(posedge clk or posedge rst)
Combinational block process (all) always @* always_comb
Non-blocking assign <= (signal in process) <= <=
Blocking assign := (variable) = =
Two-to-one mux y <= a when sel = '1' else b; assign y = sel ? a : b; same
Case case sel is when "00" => ... when others => ... case (sel) 2'b00: ... default: ... unique case or priority case
Loop generate g : for i in 0 to N-1 generate ... end generate; genvar i; generate for (...) begin : g ... end endgenerate for (genvar i = 0; ...) begin : g
Conditional generate if COND generate ... else generate (2008) if (COND) begin : g ... end inside generate same
Parameter generic (WIDTH : positive := 8) parameter WIDTH = 8 parameter int WIDTH = 8
Local constant constant C : natural := 3; localparam C = 3; localparam int C = 3;
Package package p is ... end package; plus use work.p.all; none (use include files) package p; ... endpackage plus import p::*;
Array of vectors type mem_t is array (0 to 15) of std_logic_vector(7 downto 0); reg [7:0] mem [0:15]; logic [7:0] mem [16];
Unsigned arithmetic unsigned from ieee.numeric_std plain vectors are unsigned same, or logic unsigned
Signed arithmetic signed from numeric_std reg signed [7:0] logic signed [7:0]
Integer integer, natural, positive (32-bit, ranged) integer (32-bit signed) int, shortint, longint, byte
Vector to integer to_integer(unsigned(v)) implicit implicit, or int'(v)
Integer to vector std_logic_vector(to_unsigned(i, 8)) implicit truncation 8'(i)
Resize resize(u, 16) zero or sign extend by assignment 16'(u) or assignment
Bit width of a constant (others => '0'), x"FF" {WIDTH{1'b0}}, 8'hFF '0, '1, 8'hFF
Concatenate a & b {a, b} {a, b}
Replicate (others => x) or (7 downto 0 => x) {8{x}} {8{x}}
Reduction AND and v (2008) &v &v
Synthesis attribute attribute async_reg : string; attribute async_reg of ff : signal is "TRUE"; (* ASYNC_REG = "TRUE" *) reg [1:0] ff; same as Verilog
Enumerated state type state_t is (IDLE, RUN); localparam IDLE = 2'd0, RUN = 2'd1; typedef enum logic [1:0] {IDLE, RUN} state_t;
Record or struct type hdr_t is record ... end record; none typedef struct packed { ... } hdr_t;
Function function f(x : natural) return natural is ... end function; function [7:0] f; input [7:0] x; ... endfunction function automatic logic [7:0] f(input logic [7:0] x);
Assertion (simulation) assert cond report "msg" severity error; if (!cond) $error("msg"); assert (cond) else $error("msg");
Comments -- line or /* block */ (2008) // line, /* block */ same

Register with synchronous reset and enable

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity counter is
  generic (WIDTH : positive := 8);
  port (
    clk : in  std_logic;
    rst : in  std_logic;
    en  : in  std_logic;
    q   : out std_logic_vector(WIDTH-1 downto 0)
  );
end entity;

architecture rtl of counter is
  signal q_r : unsigned(WIDTH-1 downto 0) := (others => '0');
begin
  process (clk) begin
    if rising_edge(clk) then
      if rst = '1' then
        q_r <= (others => '0');
      elsif en = '1' then
        q_r <= q_r + 1;
      end if;
    end if;
  end process;
  q <= std_logic_vector(q_r);
end architecture;
module counter #(parameter WIDTH = 8) (
  input  wire             clk,
  input  wire             rst,
  input  wire             en,
  output reg  [WIDTH-1:0] q
);
  always @(posedge clk) begin
    if (rst)     q <= {WIDTH{1'b0}};
    else if (en) q <= q + 1'b1;
  end
endmodule
module counter #(parameter int WIDTH = 8) (
  input  logic             clk,
  input  logic             rst,
  input  logic             en,
  output logic [WIDTH-1:0] q
);
  always_ff @(posedge clk) begin
    if (rst)     q <= '0;
    else if (en) q <= q + 1'b1;
  end
endmodule

The VHDL version keeps an internal unsigned signal because an out port of type std_logic_vector cannot be added to. VHDL-2008 allows reading an output port, so the intermediate signal is a choice, not a requirement.

Four-way mux with case

architecture rtl of mux4 is
begin
  with sel select y <=
    a when "00",
    b when "01",
    c when "10",
    d when others;
end architecture;
always @* begin
  case (sel)
    2'd0:    y = a;
    2'd1:    y = b;
    2'd2:    y = c;
    default: y = d;
  endcase
end
always_comb begin
  unique case (sel)
    2'd0:    y = a;
    2'd1:    y = b;
    2'd2:    y = c;
    default: y = d;
  endcase
end

unique tells the synthesiser that exactly one arm matches, which removes the priority chain. It is only an optimisation hint. The default arm is still what stops a latch.

For-generate

architecture rtl of and_tree is
begin
  g_bit : for i in 0 to N-1 generate
    y(i) <= a(i) and b(i);
  end generate;
end architecture;
genvar i;
generate
  for (i = 0; i < N; i = i + 1) begin : g_bit
    assign y[i] = a[i] & b[i];
  end
endgenerate

In SystemVerilog the generate and endgenerate keywords are optional and the genvar can be declared in the loop header. The label after begin is required by many tools so that generated instances get a hierarchical name.

Packages

package fifo_pkg is
  constant DEPTH : positive := 16;
  subtype byte_t is std_logic_vector(7 downto 0);
  function clog2_of(n : positive) return natural;
end package;

package body fifo_pkg is
  function clog2_of(n : positive) return natural is
    variable r : natural := 0;
  begin
    while 2**r < n loop
      r := r + 1;
    end loop;
    return r;
  end function;
end package body;
package fifo_pkg;
  localparam int DEPTH = 16;
  typedef logic [7:0] byte_t;
  function automatic int clog2_of(input int n);
    return $clog2(n);
  endfunction
endpackage

module fifo_user import fifo_pkg::*; (
  input  byte_t d,
  output byte_t q
);
  localparam int AW = clog2_of(DEPTH);
  assign q = d;
endmodule

Verilog-2001 has no packages. The usual workaround is a header with `define macros or parameters passed down the hierarchy. The VHDL package needs a library ieee; use ieee.std_logic_1164.all; line above it, omitted here for space.

Arrays and memories

architecture rtl of ram16x8 is
  type mem_t is array (0 to 15) of std_logic_vector(7 downto 0);
  signal mem : mem_t;
begin
  process (clk) begin
    if rising_edge(clk) then
      if we = '1' then
        mem(to_integer(unsigned(addr))) <= d;
      end if;
      q <= mem(to_integer(unsigned(addr)));
    end if;
  end process;
end architecture;
logic [7:0] mem [0:15];
always_ff @(posedge clk) begin
  if (we) mem[addr] <= d;
  q <= mem[addr];
end

Both infer a block RAM with a registered read (read-before-write on the same address). The Verilog-2001 form is identical with reg in place of logic and always @(posedge clk).

Type conversion and signedness

-- s is signed(7 downto 0), u is unsigned(7 downto 0), sum is 9 bits
sum <= std_logic_vector(resize(signed(s), 9) + signed(resize(unsigned(u), 9)));
idx <= to_integer(unsigned(u(3 downto 0)));
// s is logic signed [7:0], u is logic [7:0], sum is logic signed [8:0]
assign sum = s + $signed({1'b0, u});
assign idx = u[3:0];

The rules differ in a way that bites. In VHDL nothing converts implicitly and the compiler refuses mixed types. In Verilog and SystemVerilog an expression is signed only if every operand is signed, so one unsigned operand silently makes the whole expression unsigned and drops sign extension. The $signed({1'b0, u}) idiom keeps u positive while letting the add be signed.

Synthesis attributes

signal ff : std_logic_vector(1 downto 0);
attribute async_reg : string;
attribute async_reg of ff : signal is "TRUE";
(* ASYNC_REG = "TRUE" *) logic [1:0] ff;

Attribute names are vendor-specific: ASYNC_REG, KEEP, DONT_TOUCH, MAX_FANOUT and RAM_STYLE for AMD. Altera uses preserve, keep, ramstyle and altera_attribute. Synopsys-derived tools use syn_keep, syn_preserve and syn_ramstyle, as found in Libero SoC and Lattice Diamond. Yosys honours keep and a few of its own.

Things that do not translate

VHDL only Verilog and SystemVerilog only
Strong typing with user-defined ranged integers Four-state x propagation in arithmetic
generate with case on a generic (2008) `ifdef preprocessor and `include
Protected types and shared variables Interfaces and modports (SystemVerilog)
Physical types with units (time, but also user-defined) Classes, constrained random and coverage (SystemVerilog, simulation only)
Configurations (rarely used) Hierarchical references across modules in simulation

Sources