Ports are a set of signals that act as inputs and outputs to a particular module and are the primary way of communicating with it. Think of a module as a fabricated chip placed on a PCB and it becomes quite obvious that the only way to communicate with the chip is through its pins. Ports are like pins and are used by the design to send and receive signals from the outside world.
7 min read | Beginner
What You'll Learn
- Understand the three port types (input, output, inout) and when to use each
- Master port declaration syntax from Verilog 1995 and ANSI-C 2001 styles
- Apply signed and unsigned port attributes correctly to avoid arithmetic bugs
- Recognize common port declaration pitfalls and illegal redeclaration patterns
Types of Ports
Verilog provides three fundamental port types for module communication:
| Port Type | Description | Use Cases |
|---|---|---|
| input | The design module can only receive values from outside using its input ports | Clock signals, data inputs, control signals, reset |
| output | The design module can only send values to the outside using its output ports | Computed results, status flags, data outputs |
| inout | The design module can either send or receive values using its inout ports (bidirectional) | Memory data buses, I2C/SPI data lines, tri-state buses |
Default Port Type: Ports are by default considered as nets of type wire. If you need an output port to be driven by a register (from an always block), you must explicitly declare it as output reg.
Syntax
Ports can be declared with various attributes specifying their direction, data type, and bit width:
input [net_type] [range] list_of_names; // Input port
inout [net_type] [range] list_of_names; // Input & Output port (bidirectional)
output [net_type] [range] list_of_names; // Output port driven by a wire
output [var_type] [range] list_of_names; // Output port driven by a variable (reg)
Port Declaration Example
In the code shown below, there are three input ports, one output port and one inout port:
module my_design (
input wire clk, // Clock input (explicit wire type)
input en, // Enable input (implicit wire type)
input rw, // Read/Write control (implicit wire)
inout [15:0] data, // 16-bit bidirectional data bus
output irq // Interrupt output (implicit wire)
);
// Design behavior as Verilog code
endmodule
Port Name Uniqueness: It is illegal to use the same name for multiple ports. Each port must have a unique identifier within a module.
input aport; // First declaration - valid
input aport; // Error - already declared
output aport; // Error - already declared
Signed Ports
The signed attribute can be attached to a port declaration or a net/reg declaration or both. This is critical for correct arithmetic operations--unsigned and signed values behave differently in comparisons and arithmetic.
Default: Unsigned
Implicit nets are by default unsigned:
module my_module (
input a, // Unsigned by default
input b, // Unsigned by default
output c // Unsigned by default
);
// ports a, b, and c are by default unsigned
endmodule
Signed Port Declaration
If either the net/reg declaration or the port declaration has a signed attribute, then the signal shall be considered signed:
module signed_example (
input signed a, b, // Declare ports as signed in port list
output c
);
wire a, b; // a, b inherit 'signed' from port declaration
reg signed c; // c is signed from reg declaration
endmodule
When to Use Signed Ports: Use signed ports when performing arithmetic that involves negative numbers, such as digital signal processing (DSP), coordinate calculations, or signed comparisons. For example, a 2's complement signed 4-bit value can represent -8 to +7, while unsigned represents 0 to 15.
Port Variations Across Verilog Standards
Verilog has evolved through several IEEE standard revisions, and port declaration syntax has changed significantly between versions.
Verilog-1995 Style (Non-ANSI)
The original IEEE 1364-1995 standard required port names to be listed in the module header, with directions and types declared separately inside the module body:
// Verilog-1995: Port list first, directions declared later
module test (a, b, c); // List port names only
input [7:0] a; // inputs "a" and "b" are wires
input [7:0] b;
output [7:0] c; // output "c" by default is a wire
// Still, you can declare them again as wires to avoid confusion
wire [7:0] a;
wire [7:0] b;
wire [7:0] c;
endmodule
module test (a, b, c);
input [7:0] a, b; // Multiple inputs on one line
output [7:0] c; // By default c is of type wire
// port "c" is changed to a reg type (needed for always blocks)
reg [7:0] c;
endmodule
Verilog-1995 Characteristics:
- Port names listed in module header without types
- Port directions (
input/output/inout) declared inside module - Port can be redeclared with a different data type (
wirevsreg) - More verbose but allows flexible type changes
Verilog-2001 Style (ANSI-C)
ANSI-C style port naming was introduced in IEEE 1364-2001 and allowed the type and direction to be specified directly inside the port list:
// Verilog-2001: Direction and type in port list (ANSI-C style)
module test (
input [7:0] a, // 8-bit input
b, // "b" is also considered an 8-bit input (continues from previous line)
output [7:0] c // 8-bit output
);
// Design content
endmodule
module test (
input wire [7:0] a, // Explicit wire type (redundant, wire is default)
input wire [7:0] b,
output reg [7:0] c // Output driven by reg (for use in always blocks)
);
// Design content
endmodule
Verilog-2001 Characteristics:
- Port direction, type, and width declared in one place (more concise)
- Matches modern C/C++/SystemVerilog syntax style
- Easier to read--all port information visible at module header
- Cannot redeclare ports if type is specified in port list
Complete vs. Incomplete Declarations
If a port declaration includes a net or variable type (wire, reg), then that port is considered to be completely declared. It is illegal to redeclare the same port in a net or variable type declaration:
module test (
input [7:0] a, // a, e are implicitly declared of type wire
output reg [7:0] e // e is explicitly declared as reg
);
wire signed [7:0] a; // OKAY - 'a' has no net type in the port list, so this completes its declaration
wire [7:0] e; // ILLEGAL - declaration of 'e' is already complete
// Rest of the design code
endmodule
However, if the port declaration does not include a net or variable type, then the port can be declared again in a net or variable type declaration:
module test (
input [7:0] a, // No type specified - incomplete declaration
output [7:0] e // No type specified - incomplete declaration
);
reg [7:0] e; // OKAY - net_type was not declared before
// Rest of the design code
endmodule
Verilog-1995 vs. Verilog-2001 Comparison
| Aspect | Verilog-1995 (Non-ANSI) | Verilog-2001 (ANSI-C Style) |
|---|---|---|
| Port List | Names only in header | Full declaration in header |
| Direction Declaration | Inside module body | In port list |
| Type Declaration | Separate from direction | Combined with direction |
| Redeclaration | Allowed (can change wire to reg) | Illegal if type specified in port list |
| Readability | Less clear - info scattered | More clear - all info in one place |
| Industry Usage | Legacy code only | Standard for modern Verilog/SystemVerilog |
Real-World Application: Interface Design in SoCs
In System-on-Chip (SoC) designs, modules communicate through well-defined interfaces with specific port protocols. For example, an AXI4 master interface has over 40 ports (address channels, data channels, handshaking signals). Using Verilog-2001 ANSI-C style with clear input/output declarations in the port list makes these complex interfaces immediately understandable. Additionally, properly declaring signed vs unsigned ports prevents subtle arithmetic bugs in address calculations and data processing.
Real-World Application: Bidirectional Buses in Memory Interfaces
The inout port type is essential for memory interfaces (DDR, SRAM, Flash) where data lines must be bidirectional. In FPGA designs, inout ports map to I/O buffers (like IOBUF in Xilinx FPGAs) with tri-state control. Proper use of inout ports with tri-state logic (assign data = output_enable ? internal_data : 8'bz) is critical for bus arbitration and preventing multiple drivers from conflicting.
Common Beginner Mistakes
Mistake #1: Forgetting to declare output as reg when driving from an always block
// WRONG - output is wire by default, can't be driven from always block
module counter (
input clk,
output [3:0] count // Implicit wire type
);
always @(posedge clk) begin
count <= count + 1; // ERROR: Cannot assign to wire from procedural block
end
endmodule
Why it's problematic: By default, output ports are of type wire, which can only be driven by continuous assignments (assign) or module/gate outputs. Procedural blocks (always, initial) require reg type variables. This mismatch causes a compilation error.
Best Practice: Declare the output as output reg when it will be assigned in an always block:
// CORRECT - output declared as reg
module counter (
input clk,
output reg [3:0] count // Explicitly reg type
);
always @(posedge clk) begin
count <= count + 1; // OK: reg can be assigned in always block
end
endmodule
Mistake #2: Redeclaring ports in Verilog-2001 ANSI-C style when type is already specified
// WRONG - attempting to redeclare complete port declarations
module my_module (
input wire [7:0] a, // Complete declaration (type specified)
output reg [7:0] b // Complete declaration
);
wire [7:0] a; // ILLEGAL - 'a' already completely declared
reg [7:0] b; // ILLEGAL - 'b' already completely declared
endmodule
Why it's problematic: When using Verilog-2001 ANSI-C style with types in the port list, the ports are completely declared. Attempting to redeclare them inside the module body is illegal and will cause a compilation error or simulator-dependent behavior.
Best Practice: Only redeclare ports if the type was not specified in the port list:
// CORRECT - ports incomplete in port list, completed inside module
module my_module (
input [7:0] a, // Incomplete (no type)
output [7:0] b // Incomplete (no type)
);
// Now you can complete the declarations
wire [7:0] a; // OK - completes declaration of 'a'
reg [7:0] b; // OK - completes declaration of 'b' as reg
endmodule
Mistake #3: Confusing signed and unsigned ports in arithmetic operations
// WRONG - comparing signed and unsigned incorrectly
module comparator (
input [7:0] a, // Unsigned (default)
input signed [7:0] b, // Signed
output greater // Result of a > b
);
assign greater = (a > b); // Incorrect: mixed signedness comparison
endmodule
Why it's problematic: Verilog performs unsigned comparison when operands have different signedness. If a = 8'hFF (255 unsigned) and b = 8'hFF (-1 signed), the comparison a > b converts b to unsigned (255) and compares 255 > 255, which is false. This is almost never the intended behavior and leads to subtle bugs.
Best Practice: Ensure consistent signedness for all operands in arithmetic and comparisons:
// CORRECT - consistent signedness
module comparator (
input signed [7:0] a, // Both signed
input signed [7:0] b, // Both signed
output greater
);
assign greater = (a > b); // Correct: signed comparison (-1 < 127)
endmodule
Mistake #4: Using inout ports without proper tri-state logic
// WRONG - driving inout without tri-state
module bad_inout (
inout [7:0] data
);
reg [7:0] internal_data;
assign data = internal_data; // ERROR: Always driving, no tri-state!
endmodule
Why it's problematic: inout ports are bidirectional, meaning multiple modules may connect to the same signal. Without tri-state logic (high-impedance z state), the port is always driving, which can cause bus contention when another module tries to drive the same signal. This leads to X (unknown) values and potential hardware damage.
Best Practice: Use tri-state logic with an output enable signal:
// CORRECT - inout with tri-state control
module good_inout (
inout [7:0] data,
input output_enable
);
reg [7:0] internal_data;
// Tri-state: drive when enabled, high-Z when disabled
assign data = output_enable ? internal_data : 8'bz;
endmodule
Summary
In this tutorial, you learned:
- Three port types enable module communication:
input(receive only),output(send only), andinout(bidirectional for buses like memory data lines) - Ports default to wire type unless explicitly declared as
reg(needed when driving fromalwaysblocks) or signed (for correct arithmetic) - Verilog-1995 vs. Verilog-2001 syntax differs significantly: 1995 requires separate port lists and direction declarations, while 2001 ANSI-C style combines everything in the port list for clarity
- Complete vs. incomplete declarations determine redeclaration legality: if a type is specified in the Verilog-2001 port list, the port cannot be redeclared inside the module
- Signed ports are critical for correct arithmetic with negative numbers--mixing signed and unsigned signals in operations leads to unexpected unsigned comparisons and subtle bugs