Autocomplete in Verilog and SystemVerilog: How Semantic and AI-Assisted Code Completion Save Time and Improve Accuracy

Register transfer level (RTL) and Universal Verification Methodology (UVM) projects often involve complex structural relationships. Aggressive design parameterization often paired with intricate interconnects, parameterized interfaces and class hierarchies, and factory-based component creation make it increasingly difficult to reference design and verification elements correctly as the codebase grows.

Many iteration cycles are caused not by functional bugs, but by incorrect references introduced during editing, which are typically discovered only later during compilation or elaboration, costing both time and effort.

That is why the quality of editor assistance becomes critical. Many editors, integrated development environments (IDEs), and extensions provide some form of autocomplete, but they differ significantly in how deeply they understand your design and testbench. In practice, code completion mechanisms fall into three broad categories: text-driven, semantic-aware, and AI-assisted.

In this article, we look at how these approaches behave in real Verilog and SystemVerilog workflows, and how the completion model directly impacts accuracy, integration effort, and day-to-day productivity in RTL and UVM development.

Text-Driven vs. Semantic-Aware Autocomplete

Not all autocomplete mechanisms operate at the same level of understanding.

In lightweight, text-driven completion, suggestions are based primarily on local parsing and text matching. The editor does not resolve symbols across the project or track type and hierarchy relationships. In small examples, this can appear sufficient.

In larger RTL and UVM codebases, correctness depends on resolved relationships — module interfaces, parameter values, modport restrictions, class inheritance, and package imports. When those relationships are not modeled, completion suggestions can be incomplete or misleading.

Semantic-aware completion is driven by project-level analysis. The IDE parses your design, resolves symbols across files, and tracks type and inheritance relationships. Suggestions are derived from the actual structure of the design and verification environment — modules, interfaces, classes, and packages — rather than from text patterns alone.

Semantic Completion in Practice

Advanced hardware description language (HDL) IDEs, such as DVT IDE, compile the code and build a comprehensive project model similar to what a compiler constructs during elaboration. The integrated editor then leverages this model to provide accurate, semantic-aware completion proposals.

The following examples use DVT IDE to illustrate how semantic-aware completion behaves in common scenarios you might encounter.

RTL and IP Integration Use Cases

Instantiating a Module with Multiple Ports

Instantiating a parameterized module is a common integration task. As port lists grow, keeping connections aligned with the module’s declaration requires an increasing amount of manual effort.

module top;
  logic clk;
  logic rst_n;
  
  logic [31:0] data_in;
  logic [31:0] data_out;
  
  my_subsystem #(
    .DATA_WIDTH (32),
    .DEPTH      (64)
  ) u_my_subsystem (
    .clk     (clk),
    .rst_n   (rst_n),
    .data_in (data_in),
    .data_out (data_out),
    █
  );

endmodule

The remaining connections must match the port list of my_subsystem, defined elsewhere in the project.

With lightweight autocomplete features, proposals are based on local text and do not track the module declaration across your project. The text editor does not keep track of which ports are already connected in the instance, so aligning the remaining connections requires manually checking the module definition.

An IDE that provides semantic-aware autocomplete already understands the instantiated module and its port list. As you continue the instantiation, proposals reflect the remaining unconnected ports. Because the module declaration is resolved across the project, suggestions stay aligned with the actual interface definition.

Expanding .* Wildcard Port Connections

Wildcard connections (.*) are convenient during bring-up and quick integration, but they hide the actual connectivity and make reviews and debugging harder once the design evolves.

my_block u_block ( .* ); 
  █
);

Editors with lightweight completion cannot determine how .* will expand in this context. They have no project-level awareness of the module’s port list or which signals in the current scope match by name. As a result, understanding the actual connectivity requires manually inspecting the module declaration.

With semantic-aware completion, the IDE can resolve the declaration of my_block and the signals visible at the instantiation site. That allows it to expand .* into an explicit named port map, so you can review (and edit) real connections rather than relying on implicit name matching. Transformations like this are a common form of SystemVerilog code refactoring used to improve readability and maintainability in larger designs. Moreover, if you accidentally break the mapping by renaming a port or signal, the IDE will immediately report this as an error.

Converting Positional Port Connections to Named Connections

Positional connections are fast to write, but they depend entirely on port order. If the declaration changes, the instantiation may still compile while silently wiring signals incorrectly.

my_block u_block ( 
  clk, 
  rst_n, 
  req, 
  gnt, 
  data_i, 
  data_o
  █
); 

In editors that rely on lightweight, text-driven completion, the instantiation appears as a simple positional argument list. The editor does not relate the arguments to the module’s declared port order across the project, so converting the instance to named associations typically requires manually checking the module definition and doing a lot of typing.

When the IDE understands the module definition, the situation changes. Because the port list is resolved, the instantiation can be rewritten using explicit named connections derived from the declaration itself. Converting positional mappings to named associations is a common Verilog code refactoring technique that makes connectivity easier to review and less sensitive to port reordering. The IDE can also convert your code back to positional mappings if desired.

Working with Interfaces and Modports

Interfaces simplify connectivity, but modports restrict what is legally visible to a module. When a specific modport is selected, only a subset of the interface signals is accessible.

interface bus_if;
  logic clk;
  logic valid;
  logic data;

  modport master (input clk, output valid, output data);
  modport slave  (input clk, input valid, input data);

endinterface
module consumer (bus_if.slave bus);
	
  initial begin
    bus. █
  end

endmodule

In this case, bus is bound to the slave modport, which restricts the visible signals.

In tools limited to lightweight parsing, completion may reflect the full interface definition without enforcing the selected modport restrictions. As a result, proposals can include members that are not legally accessible through the bound modport.

When completion is semantic-aware, the selected modport is part of the resolved model. Suggestions are restricted to the signals exposed by bus_if.slave, matching the actual access rules enforced by the compiler.

Completing Enum Values

Enums are widely used for state machines, configuration fields, and transaction attributes. The valid values are defined by the enum type, not by the local scope.

typedef enum logic [1:0] { 
  IDLE, 
  BUSY, 
  ERROR
} state_e;

state_e current_state;

always_comb begin
  case (current_state)
    IDLE: begin
      if (start)
        next_state = █;
      else
        next_state = IDLE;
    end

At the assignment point, only IDLE, BUSY, and ERROR are legal values.

Text-based completion simply proposes tokens that match what you start typing. Enum literals may appear, but so may unrelated identifiers defined elsewhere in the file or project. The tool does not reliably evaluate the resolved type of current_state before suggesting candidates.

In a semantic-aware IDE, the type of current_state is already resolved. When completion is triggered after the assignment operator, suggestions are restricted to the literals defined in state_e. The editor can therefore restrict suggestions to the legal value space defined by the enum.

Accessing Struct Members

Packed structs are widely used for transactions, configuration fields, and bundled signals. Member access depends entirely on the resolved type of the variable.

typedef struct packed {
  logic [7:0]  opcode;
  logic [15:0] addr;
} packet_t;

packet_t pkt;

always_comb begin
  pkt.█
end

Here, completion should reflect the members of packet_t.

Text-driven completion proposes identifiers based on local matches rather than the declared type of pkt. Without project-wide type tracking, suggestions may include unrelated symbols alongside the struct’s actual members.

A semantic-aware IDE resolves pkt to packet_t within the project’s type system. Suggestions are therefore restricted to opcode and addr, consistent with the declared structure.

Extracting Logic into a Reusable Module

As designs evolve, you need to refactor logic into reusable modules to maintain a clean and scalable hierarchy.

always @(posedge wb_clk_i or posedge wb_rst_i)
  █

In editors limited to text-driven completion, this process is largely manual. Engineers must identify the signals that are part of the new interface, define and instantiate the new module, and ensure all signals are correctly connected.

A semantic-aware IDE understands the surrounding design context and can assist in generating the new module interface and updating connections accordingly, reducing the effort required to maintain a consistent design hierarchy.

If you'd like to explore these semantic autocomplete capabilities in more detail, the following documentation walks you through the entire workflow.

Verification Use Cases

In larger UVM environments, most of the usable application programming interface (API) lives several levels up the hierarchy, while work happens inside derived components.

class my_driver extends uvm_driver #(my_transaction);
	
  virtual function void build_phase(uvm_phase phase);
    super.█
  endfunction

endclass

Completion must account for the resolved inheritance hierarchy of uvm_driver #(my_transaction).

In lightweight completion tools, super is treated as a simple token, and suggestions are based on local matches rather than the actual base class definition.

With semantic-aware completion, the IDE resolves the full class hierarchy and proposes the methods and members inherited from uvm_driver and its ancestors. This eliminates the need to manually navigate the UVM source to confirm available APIs.

Overriding Methods in Derived UVM Components

Overriding base class methods is a routine task in UVM. The override must match the original method name and signature exactly.

class my_driver extends uvm_driver #(my_transaction);
  █

endclass

When adding an override, you typically start typing the method name:

virtual function void build_phase(uvm_phase phase);█ 
endfunction

At this point, the correctness of the override depends on the resolved base class. The method must exist in uvm_driver #(my_transaction) or one of its ancestors, and its signature must match.

In editors that offer text-driven autocomplete features, inherited methods are not derived from the resolved class hierarchy. To override a method, you typically navigate to the base class to confirm its declaration and copy the exact signature.

When the IDE resolves the full inheritance hierarchy, it can propose the virtual methods inherited from the base class. Selecting one inserts the correct method name and signature as declared, reducing the risk of subtle mismatches.

Working with uvm_config_db

uvm_config_db relies on type parameters and string keys to pass configuration values across hierarchy levels, often involving virtual interfaces.

virtual bus_if vif;
	
  uvm_config_db#(virtual bus_if)::get(this, "", "vif",█ vif);

Correctness depends on matching both the type parameter and the target variable.

In text-driven completion, arguments are treated as ordinary words. Suggestions are based on local text patterns and do not take the expected type into account. As a result, the editor may propose identifiers that are not compatible with the declared type parameter.

In a semantic-aware IDE, the type parameter and the variable type are resolved as part of the project model. This allows the IDE to prioritize or restrict suggestions to variables of type virtual bus_if, reducing the risk of mismatches when retrieving configuration objects.

While uvm_config_db keys remain string conventions, semantic awareness helps ensure consistency in how configuration values are defined and retrieved across the codebase.

Completing include Directives and Macro References

Large verification environments rely heavily on SystemVerilog macros and include files to assemble configuration, sequences, and utilities.

`include "uvm_macros.svh"  
`include "ag█"

Here, the path must match a file available in the project’s include directories.

In lightweight completion tools, the string inside an `include directive is treated as a file path. Suggestions may come from the workspace filesystem, but they do not reflect your project’s configured include directories or preprocessor setup.

A semantic-aware IDE, by contrast, analyzes the project using the same include paths and defines used for compilation. As a result, completion proposals align with what the compiler is configured to resolve, reducing include mismatches that otherwise lead to cascaded errors.

To see how DVT IDE’s autocomplete works in practice, try it yourself!

Beyond Semantic Autocomplete: AI-Assisted Code Completion

Semantic-aware completion removes much of the friction involved in referencing design elements correctly. However, RTL and verification development still requires writing new code: expanding partial constructs, implementing scaffolding, and translating design intent into SystemVerilog or UVM implementations.

In these situations, selecting existing project symbols is only part of the task. You still need to implement additional functionality while keeping it consistent with the rest of the design.

AI-assisted completion supports this part of the workflow by generating suggestions directly in the IDE. Unlike text-driven or semantic-aware completion, which help reference existing elements of the project, AI assistance focuses on producing new code based on the surrounding context.

However, the usefulness of AI suggestions depends heavily on how much awareness the tool has of the overall structure of the project.

Generic Test-Based vs Specialized compiler-backed AI Assistants

General-purpose AI coding assistants generate suggestions primarily from the text around the cursor. As you write code, they may propose the next statements, expand repetitive constructs, or generate scaffolding that follows common coding patterns. This can be helpful when drafting new implementations or filling in routine pieces of code.

These suggestions rely mostly on local context, so they are not always aligned with the structure of your current project. The assistant may propose symbols that do not exist in the design, reference APIs incorrectly, or generate code that requires further adjustments.

Specialized HDL development environments, such as DVT IDE, take a different approach by integrating AI assistance into editors that already maintain a semantic understanding of the project. In these environments, suggestions are generated within the same project-aware context used for features such as symbol resolution and navigation, allowing them to better reflect the types, interfaces, and APIs defined across the project codebase.

AI-Assisted Completion Examples

The following examples illustrate how AI-assisted completion within DVT IDE can support common RTL and verification development tasks.

Completing a State Machine

When writing RTL state machines, you typically start by defining the states and drafting the case structure.

typedef enum logic [1:0] { 
  IDLE, 
  BUSY, 
  ERROR
} state_e;

state_e state;

always_ff @(posedge clk or negedge rst_n) begin 
  if (!rst_n) begin
    state <= IDLE;
  end else begin
    case (state)
      █
	endcase
  end
end

Here, AI-assisted completion can generate a suggested case body based on the surrounding code. For example, it may propose branches for the enum values and a default case, giving you a draft that you can refine as the state transitions are implemented.

Generating a UVM Component Skeleton

New UVM components typically follow the same initial class structure across implementations.

class apb_master_driver extends uvm_driver #(apb_transfer);
  █
endclass

At this point, DVT IDE’s AI assistant may suggest an initial implementation of the class. This can include elements such as the registration macro, a constructor, and common phase method stubs (for example build_phase and run_phase), providing a base structure that you can then tailor to the specific role of the driver.

Expanding a Partial Implementation

During development, it’s common to sketch out the structure of a block or the core idea of a sequence first and fill in the details later.

class uart0_rx_fifo_overflow_vseq █ extends uvm_sequence; 

In this situation, AI suggestions may include a possible implementation for the sequence body based on the surrounding context. The generated code might contain field declarations, the typically required constructor and factory registrations, constraints, and a possible implementation of the scenario, giving you a solid draft that you can build on.

Generating RTL Interconnect Structures

In complex SoC designs, connecting multiple modules through structured interconnects becomes increasingly difficult as the number of signals and hierarchy levels grows.

module dma;
  █
endmodule

Manually wiring and maintaining these connections is time-consuming and error-prone, especially when interfaces evolve or signal widths change.

AI-assisted completion can generate interconnect scaffolding based on the surrounding modules and signals. This provides a starting point that follows common design patterns, allowing you to focus on refining and verifying connectivity rather than writing boilerplate code.

To learn more about how DVT IDE’s AI assistant supports SystemVerilog and UVM development, explore the following resources:

Conclusion

As RTL and verification projects grow in complexity, the quality of editor assistance becomes increasingly important. Text-driven completion helps with language constructs, semantic-aware completion ensures suggestions reflect the structure of the design, and AI-assisted completion helps generate new code based on the surrounding context. Together, these capabilities reduce development friction, improve code accuracy, and help engineers move more quickly from idea to implementation.

DVT IDE offers all three capabilities — and many more — in a single development environment. Want to see it in action?