sintaxis-basica-verilog-modulos-puertos

Basic Verilog Syntax: Modules, Ports, Wire and Reg

  • 6 min

A Verilog module is a hardware block with ports and internal logic that we can connect to other modules to build a larger design.

Today we will lay a solid foundation. We are going to dissect the grammar of Verilog.

If you come from C or C++, you will see many curly braces { and semicolons ;, but don’t be fooled: the meaning is completely different. Here we don’t define functions and variables; we define black boxes and wires.

The Module (module): The Fundamental Unit

In Verilog, everything revolves around the concept of a Module. Think of a module as a physical chip or an electronic component you hold in your hand.

  • It has a casing (the code).
  • It has metal pins to connect (the ports).
  • It has a circuit inside (the logic).

The basic structure is always this:

module module_name (
    // Here we define the "pins" (Ports)
    input wire a,
    input wire b,
    output wire y
);

    // --- Here goes the internal circuit ---
    assign y = a & b; // Example: An AND gate

endmodule
Copied!

Unlike functions in C++, modules are not “called”. They are instantiated.

That is, you “solder” a copy of that module into your design. If you need two counters, you will have two physical copies of the counter circuit in your FPGA.

Ports: input, output, and inout

Ports are the communication interface of our module with the outside world (or with other modules). They are equivalent to function arguments, but with a physical direction.

There are three basic types:

  1. input: Signals entering the module. We can only read them.
  2. output: Signals leaving the module. We write to them.
  3. inout: Bidirectional signals (like I2C SDA pins). These are more complex and require “high-impedance” logic (Tri-state); for now, we will leave them aside.

Buses and Vectors

In hardware, we rarely work with a single bit. We typically work with groups of bits (data buses, counters, addresses).

To define a bus, we use the notation [MSB:LSB] (Most Significant Bit : Least Significant Bit).

input wire clk,             // 1 single bit (scalar)
input wire [7:0] data_in,   // 8-bit bus (from 7 to 0)
output wire [15:0] result   // 16-bit bus
Copied!

Beware of the order: [7:0] means bit 7 is the most significant (conceptual Big Endian).

It is possible to write [0:7], and some designs require it, but mixing both orientations easily causes errors. Maintain a consistent convention and always check which index represents each end of the bus.

Data Types: wire and reg

This is the point where 90% of beginners get stuck. In Verilog, there are two main types of data “containers”: wire and reg.

Understanding the difference is important because it defines how values are assigned.

The wire Type (Cable)

The wire type represents a physical connection, a conducting wire.

  • It has no memory. It only transports a value from point A to point B.
  • It is used to connect modules or outputs of combinational logic.
  • It can receive a continuous assignment with assign, or be driven by the output of another module or a primitive.
wire cable_a;
assign cable_a = button_input & switch; // Continuous connection
Copied!

A wire without any driver does not retain the previous value: in simulation it remains in high impedance (Z).

The reg Type (Procedural Variable)

The reg type represents a variable to which we assign a value from a procedural block.

  • It retains its last value in the simulation until the block assigns another one to it.
  • It can only be assigned inside procedural blocks (always, initial).

WATCH OUT! The name is misleading.

A reg does NOT always become a physical Flip-Flop or a hardware register.

It is simply a “Verilog variable”. If you use it in combinational logic, the synthesizer will turn it into a simple wire. If you use it with a clock (posedge clk), then it will indeed become a Flip-Flop.

Practical Criterion for Choosing

When in doubt, use this mnemonic rule:

  1. Are you going to use assign? => Use wire.
  2. Are you going to use an always block? => Use reg.
module type_example (
    input wire clk,
    input wire a,
    output wire direct_output, // Will be assigned with assign
    output reg memory_output   // Will be assigned inside an always
);

    // Usage of WIRE (Simple combinational logic)
    // "direct_output is EQUAL to 'a' at all times"
    assign direct_output = ~a;

    // Usage of REG (Sequential logic)
    // "Inside the always block, we update the variable"
    always @(posedge clk) begin
        memory_output <= a;
    end

endmodule
Copied!

Number Representation (Literals)

In Arduino we write int x = 10;. In Verilog we need to be explicit about the bit width of our numbers to avoid surprises with overflow.

The format is: <size_in_bits>'<base><value>

FormatDescriptionDecimal Value
8'd108 bits, decimal10
8'h0A8 bits, hexadecimal10
8'b000010108 bits, binary10
1'b11 bit, binary1 (True)
1'b01 bit, binary0 (False)

We can also use the underscore _ to improve readability for long numbers, just like in modern versions of C++ or Java.

reg [31:0] large_counter = 32'b1010_0011_1111_0000_1010_0011_1111_0000;
Copied!

Integrative Example: A Multiplexer

To conclude, let’s look at a module that combines everything we’ve learned: a 2-to-1 Multiplexer.

It is a circuit that has two inputs (d0, d1) and a selection signal (sel). If sel is 0, d0 goes to the output. If sel is 1, d1 goes to the output.

module multiplexer (
    input wire d0,      // Data input 0
    input wire d1,      // Data input 1
    input wire sel,     // Selector
    output wire y       // Output
);

    // Implementation using boolean logic with assign (wires)
    // y = (d1 AND sel) OR (d0 AND NOT sel)
    assign y = (d1 & sel) | (d0 & ~sel);

    /*
    Alternative using the ternary operator (like in C):
    assign y = sel ? d1 : d0;
    */

endmodule
Copied!