Forum Discussion

srimathi's avatar
srimathi
Icon for New Contributor rankNew Contributor
4 months ago

UART functionality in altera max II EMP240 CPLD

Hi everyone,

I'm currently working on implementing UART communication on an Altera MAX II CPLD using Verilog. As I'm still learning, I would really appreciate it if anyone could share UART transmitter and receiver Verilog code. Thanks in advance.

7 Replies

  • KennyT_altera's avatar
    KennyT_altera
    Icon for Super Contributor rankSuper Contributor

    I'll provide you with synthesizable UART transmitter and receiver Verilog code suitable for Altera MAX II CPLD implementation. But you will need to write your testbench to test out the code:


    ## UART Transmitter


    ```verilog

    module uart_tx #(

    parameter CLOCK_FREQ = 50000000, // 50 MHz clock

    parameter BAUD_RATE = 9600 // 9600 baud

    )(

    input clk,

    input rst_n,

    input [7:0] tx_data,

    input tx_start,

    output reg tx,

    output reg tx_busy

    );


    localparam BAUD_TICKS = CLOCK_FREQ / BAUD_RATE;

    localparam COUNTER_WIDTH = $clog2(BAUD_TICKS);


    reg [COUNTER_WIDTH-1:0] baud_counter;

    reg [3:0] bit_counter;

    reg [9:0] tx_shift_reg; // Start bit + 8 data bits + Stop bit

    reg baud_tick;


    // Baud rate generator

    always @(posedge clk or negedge rst_n) begin

    if (!rst_n) begin

    baud_counter <= 0;

    baud_tick <= 1'b0;

    end else begin

    if (baud_counter == BAUD_TICKS - 1) begin

    baud_counter <= 0;

    baud_tick <= 1'b1;

    end else begin

    baud_counter <= baud_counter + 1;

    baud_tick <= 1'b0;

    end

    end

    end


    // UART transmitter state machine

    always @(posedge clk or negedge rst_n) begin

    if (!rst_n) begin

    tx <= 1'b1; // Idle high

    tx_busy <= 1'b0;

    bit_counter <= 0;

    tx_shift_reg <= 10'h3FF; // All ones (idle)

    end else begin

    if (tx_start && !tx_busy) begin

    tx_shift_reg <= {1'b1, tx_data, 1'b0}; // Stop + Data + Start

    tx_busy <= 1'b1;

    bit_counter <= 0;

    end else if (tx_busy && baud_tick) begin

    tx <= tx_shift_reg[0];

    tx_shift_reg <= {1'b1, tx_shift_reg[9:1]};

    if (bit_counter == 9) begin

    tx_busy <= 1'b0;

    bit_counter <= 0;

    end else begin

    bit_counter <= bit_counter + 1;

    end

    end

    end

    end


    endmodule

    ```


    ## UART Receiver


    ```verilog

    module uart_rx #(

    parameter CLOCK_FREQ = 50000000, // 50 MHz clock

    parameter BAUD_RATE = 9600 // 9600 baud

    )(

    input clk,

    input rst_n,

    input rx,

    output reg [7:0] rx_data,

    output reg rx_valid

    );


    localparam BAUD_TICKS = CLOCK_FREQ / BAUD_RATE;

    localparam HALF_BAUD_TICKS = BAUD_TICKS / 2;

    localparam COUNTER_WIDTH = $clog2(BAUD_TICKS);


    reg [COUNTER_WIDTH-1:0] baud_counter;

    reg [3:0] bit_counter;

    reg [7:0] rx_shift_reg;

    reg baud_tick;

    reg rx_sync1, rx_sync2; // Synchronizer for rx input


    // Input synchronizer

    always @(posedge clk or negedge rst_n) begin

    if (!rst_n) begin

    rx_sync1 <= 1'b1;

    rx_sync2 <= 1'b1;

    end else begin

    rx_sync1 <= rx;

    rx_sync2 <= rx_sync1;

    end

    end


    // State machine states

    localparam IDLE = 2'b00;

    localparam START = 2'b01;

    localparam DATA = 2'b10;

    localparam STOP = 2'b11;


    reg [1:0] state;


    // Baud rate counter

    always @(posedge clk or negedge rst_n) begin

    if (!rst_n) begin

    baud_counter <= 0;

    baud_tick <= 1'b0;

    end else begin

    if (state == IDLE) begin

    baud_counter <= 0;

    baud_tick <= 1'b0;

    end else if (baud_counter == BAUD_TICKS - 1) begin

    baud_counter <= 0;

    baud_tick <= 1'b1;

    end else begin

    baud_counter <= baud_counter + 1;

    baud_tick <= 1'b0;

    end

    end

    end


    // UART receiver state machine

    always @(posedge clk or negedge rst_n) begin

    if (!rst_n) begin

    state <= IDLE;

    bit_counter <= 0;

    rx_shift_reg <= 0;

    rx_data <= 0;

    rx_valid <= 1'b0;

    end else begin

    rx_valid <= 1'b0; // Default: no valid data

    case (state)

    IDLE: begin

    if (!rx_sync2) begin // Start bit detected

    state <= START;

    baud_counter <= 0;

    end

    end

    START: begin

    if (baud_counter == HALF_BAUD_TICKS) begin

    if (!rx_sync2) begin // Confirm start bit

    state <= DATA;

    bit_counter <= 0;

    baud_counter <= 0;

    end else begin

    state <= IDLE; // False start bit

    end

    end

    end

    DATA: begin

    if (baud_tick) begin

    rx_shift_reg <= {rx_sync2, rx_shift_reg[7:1]};

    if (bit_counter == 7) begin

    state <= STOP;

    bit_counter <= 0;

    end else begin

    bit_counter <= bit_counter + 1;

    end

    end

    end

    STOP: begin

    if (baud_tick) begin

    if (rx_sync2) begin // Valid stop bit

    rx_data <= rx_shift_reg;

    rx_valid <= 1'b1;

    end

    state <= IDLE;

    end

    end

    endcase

    end

    end


    endmodule

    ```


    ## Top-Level UART Module


    ```verilog

    module uart_top #(

    parameter CLOCK_FREQ = 50000000,

    parameter BAUD_RATE = 9600

    )(

    input clk,

    input rst_n,

    // TX interface

    input [7:0] tx_data,

    input tx_start,

    output tx_busy,

    output uart_tx,

    // RX interface

    input uart_rx,

    output [7:0] rx_data,

    output rx_valid

    );


    uart_tx #(

    .CLOCK_FREQ(CLOCK_FREQ),

    .BAUD_RATE(BAUD_RATE)

    ) tx_inst (

    .clk(clk),

    .rst_n(rst_n),

    .tx_data(tx_data),

    .tx_start(tx_start),

    .tx(uart_tx),

    .tx_busy(tx_busy)

    );


    uart_rx #(

    .CLOCK_FREQ(CLOCK_FREQ),

    .BAUD_RATE(BAUD_RATE)

    ) rx_inst (

    .clk(clk),

    .rst_n(rst_n),

    .rx(uart_rx),

    .rx_data(rx_data),

    .rx_valid(rx_valid)

    );


    endmodule

    ```


    ## Key Features


    - **Parameterizable**: Clock frequency and baud rate can be configured

    - **Synchronous Design**: Uses proper clock domain design suitable for MAX II

    - **Input Synchronization**: RX input is properly synchronized to avoid metastability

    - **Standard UART Protocol**: 1 start bit, 8 data bits, 1 stop bit, no parity

    - **Handshaking**: TX has busy signal, RX has valid signal


    ## Usage Example


    ```verilog

    // Instantiate in your top-level design

    uart_top #(

    .CLOCK_FREQ(50000000), // 50 MHz

    .BAUD_RATE(115200) // 115200 baud

    ) uart_inst (

    .clk(clk_50mhz),

    .rst_n(reset_n),

    .tx_data(data_to_send),

    .tx_start(send_enable),

    .tx_busy(uart_busy),

    .uart_tx(uart_tx_pin),

    .uart_rx(uart_rx_pin),

    .rx_data(received_data),

    .rx_valid(data_ready)

    );

    ```





  • FvM's avatar
    FvM
    Icon for Super Contributor rankSuper Contributor

    Hi,
    compiling the Verilog uart code, I see multiple drivers for baud_counter in uart_rx module. Can be fixed by merging Baud rate counter and UART receiver state machine always blocks.

    Regards
    Frank

    • srimathi's avatar
      srimathi
      Icon for New Contributor rankNew Contributor

      Hi,

      Is that code is working for you, I barely see any waveform on the tx line.

  • KennyT_altera's avatar
    KennyT_altera
    Icon for Super Contributor rankSuper Contributor

    How about try the below:


    module uart_rx #(

    parameter CLOCK_FREQ = 50000000, // 50 MHz clock

    parameter BAUD_RATE = 9600 // 9600 baud

    )(

    input clk,

    input rst_n,

    input rx,

    output reg [7:0] rx_data,

    output reg rx_valid

    );


    localparam BAUD_TICKS = CLOCK_FREQ / BAUD_RATE;

    localparam HALF_BAUD_TICKS = BAUD_TICKS / 2;

    localparam COUNTER_WIDTH = $clog2(BAUD_TICKS);


    reg [COUNTER_WIDTH-1:0] baud_counter;

    reg [3:0] bit_counter;

    reg [7:0] rx_shift_reg;

    reg baud_tick;

    reg rx_sync1, rx_sync2; // Synchronizer for rx input


    // Input synchronizer

    always @(posedge clk or negedge rst_n) begin

    if (!rst_n) begin

    rx_sync1 <= 1'b1;

    rx_sync2 <= 1'b1;

    end else begin

    rx_sync1 <= rx;

    rx_sync2 <= rx_sync1;

    end

    end


    // State machine states

    localparam IDLE = 2'b00;

    localparam START = 2'b01;

    localparam DATA = 2'b10;

    localparam STOP = 2'b11;


    reg [1:0] state;


    // Combined baud counter and UART receiver state machine

    always @(posedge clk or negedge rst_n) begin

    if (!rst_n) begin

    state <= IDLE;

    bit_counter <= 0;

    rx_shift_reg <= 0;

    rx_data <= 0;

    rx_valid <= 1'b0;

    baud_counter <= 0;

    baud_tick <= 1'b0;

    end else begin

    rx_valid <= 1'b0; // Default: no valid data

    baud_tick <= 1'b0; // Default: no baud tick

    case (state)

    IDLE: begin

    baud_counter <= 0;

    if (!rx_sync2) begin // Start bit detected

    state <= START;

    baud_counter <= 0;

    end

    end

    START: begin

    if (baud_counter == HALF_BAUD_TICKS) begin

    if (!rx_sync2) begin // Confirm start bit

    state <= DATA;

    bit_counter <= 0;

    baud_counter <= 0;

    end else begin

    state <= IDLE; // False start bit

    end

    end else begin

    baud_counter <= baud_counter + 1;

    end

    end

    DATA: begin

    if (baud_counter == BAUD_TICKS - 1) begin

    baud_counter <= 0;

    baud_tick <= 1'b1;

    rx_shift_reg <= {rx_sync2, rx_shift_reg[7:1]};

    if (bit_counter == 7) begin

    state <= STOP;

    bit_counter <= 0;

    end else begin

    bit_counter <= bit_counter + 1;

    end

    end else begin

    baud_counter <= baud_counter + 1;

    end

    end

    STOP: begin

    if (baud_counter == BAUD_TICKS - 1) begin

    baud_counter <= 0;

    baud_tick <= 1'b1;

    if (rx_sync2) begin // Valid stop bit

    rx_data <= rx_shift_reg;

    rx_valid <= 1'b1;

    end

    state <= IDLE;

    end else begin

    baud_counter <= baud_counter + 1;

    end

    end

    endcase

    end

    end


    endmodule


    You're correct! The issue is that both the baud rate counter logic and the state machine are trying to drive the `baud_counter` signal, creating multiple drivers. Here's the fixed UART receiver code with the two always blocks merged:


    ## Fixed UART Receiver Code


    ```verilog

    module uart_rx #(

    parameter CLOCK_FREQ = 50000000, // 50 MHz clock

    parameter BAUD_RATE = 9600 // 9600 baud

    )(

    input clk,

    input rst_n,

    input rx,

    output reg [7:0] rx_data,

    output reg rx_valid

    );


    localparam BAUD_TICKS = CLOCK_FREQ / BAUD_RATE;

    localparam HALF_BAUD_TICKS = BAUD_TICKS / 2;

    localparam COUNTER_WIDTH = $clog2(BAUD_TICKS);


    reg [COUNTER_WIDTH-1:0] baud_counter;

    reg [3:0] bit_counter;

    reg [7:0] rx_shift_reg;

    reg baud_tick;

    reg rx_sync1, rx_sync2; // Synchronizer for rx input


    // Input synchronizer

    always @(posedge clk or negedge rst_n) begin

    if (!rst_n) begin

    rx_sync1 <= 1'b1;

    rx_sync2 <= 1'b1;

    end else begin

    rx_sync1 <= rx;

    rx_sync2 <= rx_sync1;

    end

    end


    // State machine states

    localparam IDLE = 2'b00;

    localparam START = 2'b01;

    localparam DATA = 2'b10;

    localparam STOP = 2'b11;


    reg [1:0] state;


    // Combined baud counter and UART receiver state machine

    always @(posedge clk or negedge rst_n) begin

    if (!rst_n) begin

    state <= IDLE;

    bit_counter <= 0;

    rx_shift_reg <= 0;

    rx_data <= 0;

    rx_valid <= 1'b0;

    baud_counter <= 0;

    baud_tick <= 1'b0;

    end else begin

    rx_valid <= 1'b0; // Default: no valid data

    baud_tick <= 1'b0; // Default: no baud tick

    case (state)

    IDLE: begin

    baud_counter <= 0;

    if (!rx_sync2) begin // Start bit detected

    state <= START;

    baud_counter <= 0;

    end

    end

    START: begin

    if (baud_counter == HALF_BAUD_TICKS) begin

    if (!rx_sync2) begin // Confirm start bit

    state <= DATA;

    bit_counter <= 0;

    baud_counter <= 0;

    end else begin

    state <= IDLE; // False start bit

    end

    end else begin

    baud_counter <= baud_counter + 1;

    end

    end

    DATA: begin

    if (baud_counter == BAUD_TICKS - 1) begin

    baud_counter <= 0;

    baud_tick <= 1'b1;

    rx_shift_reg <= {rx_sync2, rx_shift_reg[7:1]};

    if (bit_counter == 7) begin

    state <= STOP;

    bit_counter <= 0;

    end else begin

    bit_counter <= bit_counter + 1;

    end

    end else begin

    baud_counter <= baud_counter + 1;

    end

    end

    STOP: begin

    if (baud_counter == BAUD_TICKS - 1) begin

    baud_counter <= 0;

    baud_tick <= 1'b1;

    if (rx_sync2) begin // Valid stop bit

    rx_data <= rx_shift_reg;

    rx_valid <= 1'b1;

    end

    state <= IDLE;

    end else begin

    baud_counter <= baud_counter + 1;

    end

    end

    endcase

    end

    end


    endmodule

    ```


    ## Key Changes Made


    1. **Merged Always Blocks**: Combined the separate baud counter and state machine always blocks into a single always block to eliminate multiple drivers for `baud_counter`.


    2. **State-Specific Counter Logic**: The baud counter now resets and increments based on the current state:

    - **IDLE**: Counter is reset to 0

    - **START**: Counter increments until half baud period

    - **DATA/STOP**: Counter increments until full baud period, then resets


    3. **Simplified Baud Tick Generation**: The `baud_tick` signal is now generated inline with the counter logic rather than in a separate process.


    ## Complete Updated Top-Level Module


    The UART transmitter and top-level module remain the same. Just replace the `uart_rx` module with the fixed version above.


  • KennyT_altera's avatar
    KennyT_altera
    Icon for Super Contributor rankSuper Contributor

    See if the below testbench will help you also?


    `timescale 1ns/1ps


    module tb_uart_top;

    // Parameters

    parameter CLOCK_FREQ = 50000000; // 50 MHz

    parameter BAUD_RATE = 115200; // 115200 baud for faster simulation

    parameter CLK_PERIOD = 20.0; // 50 MHz = 20ns period

    parameter BIT_PERIOD = 1000000000.0 / BAUD_RATE; // Bit period in ns

    // DUT signals

    reg clk;

    reg rst_n;

    // TX interface

    reg [7:0] tx_data;

    reg tx_start;

    wire tx_busy;

    wire uart_tx;

    // RX interface

    reg uart_rx;

    wire [7:0] rx_data;

    wire rx_valid;

    // Test variables

    reg [7:0] test_data_queue[$];

    reg [7:0] received_data_queue[$];

    integer test_count = 0;

    integer error_count = 0;

    // Clock generation

    initial begin

    clk = 0;

    forever #(CLK_PERIOD/2) clk = ~clk;

    end

    // DUT instantiation

    uart_top #(

    .CLOCK_FREQ(CLOCK_FREQ),

    .BAUD_RATE(BAUD_RATE)

    ) dut (

    .clk(clk),

    .rst_n(rst_n),

    .tx_data(tx_data),

    .tx_start(tx_start),

    .tx_busy(tx_busy),

    .uart_tx(uart_tx),

    .uart_rx(uart_rx),

    .rx_data(rx_data),

    .rx_valid(rx_valid)

    );

    // Task to send a byte via UART TX

    task send_byte(input [7:0] data);

    begin

    @(posedge clk);

    while (tx_busy) @(posedge clk); // Wait for TX to be ready

    tx_data = data;

    tx_start = 1'b1;

    @(posedge clk);

    tx_start = 1'b0;

    test_data_queue.push_back(data);

    $display("Time %0t: Sending byte 0x%02h", $time, data);

    @(posedge clk);

    while (tx_busy) @(posedge clk); // Wait for transmission to complete

    end

    endtask

    // Task to simulate receiving a byte via UART RX

    task receive_byte(input [7:0] data);

    integer i;

    begin

    $display("Time %0t: Simulating RX byte 0x%02h", $time, data);

    // Send start bit (0)

    uart_rx = 1'b0;

    #(BIT_PERIOD);

    // Send 8 data bits (LSB first)

    for (i = 0; i < 8; i = i + 1) begin

    uart_rx = data[i];

    #(BIT_PERIOD);

    end

    // Send stop bit (1)

    uart_rx = 1'b1;

    #(BIT_PERIOD);

    end

    endtask

    // Monitor RX valid data

    always @(posedge clk) begin

    if (rx_valid) begin

    received_data_queue.push_back(rx_data);

    $display("Time %0t: Received byte 0x%02h", $time, rx_data);

    end

    end

    // Task to verify received data

    task verify_received_data();

    reg [7:0] expected, actual;

    begin

    while (test_data_queue.size() > 0 && received_data_queue.size() > 0) begin

    expected = test_data_queue.pop_front();

    actual = received_data_queue.pop_front();

    if (expected == actual) begin

    $display("✓ PASS: Expected 0x%02h, Got 0x%02h", expected, actual);

    end else begin

    $display("✗ FAIL: Expected 0x%02h, Got 0x%02h", expected, actual);

    error_count = error_count + 1;

    end

    test_count = test_count + 1;

    end

    end

    endtask

    // Loopback connection for self-test

    always @(*) begin

    uart_rx = uart_tx;

    end

    // Main test sequence

    initial begin

    // Initialize signals

    rst_n = 1'b0;

    tx_data = 8'h00;

    tx_start = 1'b0;

    uart_rx = 1'b1; // UART idle state

    $display("=== UART Testbench Starting ===");

    $display("Clock Frequency: %0d Hz", CLOCK_FREQ);

    $display("Baud Rate: %0d", BAUD_RATE);

    $display("Bit Period: %0.2f ns", BIT_PERIOD);

    // Reset sequence

    #(CLK_PERIOD * 10);

    rst_n = 1'b1;

    #(CLK_PERIOD * 10);

    // Test 1: Basic loopback test

    $display("\n=== Test 1: Basic Loopback Test ===");

    send_byte(8'h55); // Alternating pattern

    send_byte(8'hAA); // Inverse alternating pattern

    send_byte(8'h00); // All zeros

    send_byte(8'hFF); // All ones

    // Wait for data to be received

    #(BIT_PERIOD * 20);

    verify_received_data();

    // Test 2: ASCII characters

    $display("\n=== Test 2: ASCII Character Test ===");

    send_byte(8'h48); // 'H'

    send_byte(8'h65); // 'e'

    send_byte(8'h6C); // 'l'

    send_byte(8'h6C); // 'l'

    send_byte(8'h6F); // 'o'

    send_byte(8'h20); // ' '

    send_byte(8'h55); // 'U'

    send_byte(8'h41); // 'A'

    send_byte(8'h52); // 'R'

    send_byte(8'h54); // 'T'

    // Wait for data to be received

    #(BIT_PERIOD * 30);

    verify_received_data();

    // Test 3: Back-to-back transmission

    $display("\n=== Test 3: Back-to-Back Transmission ===");

    for (int i = 0; i < 16; i++) begin

    send_byte(i);

    end

    // Wait for all data to be received

    #(BIT_PERIOD * 50);

    verify_received_data();

    // Test 4: RX-only test (disconnect loopback)

    $display("\n=== Test 4: RX-Only Test ===");

    // Disconnect loopback for manual RX testing

    force uart_rx = 1'b1;

    #(CLK_PERIOD * 5);

    // Manually send some bytes to RX

    release uart_rx;

    receive_byte(8'hA5);

    receive_byte(8'h5A);

    receive_byte(8'h96);

    // Wait and check

    #(BIT_PERIOD * 10);

    // Final results

    #(BIT_PERIOD * 20);

    $display("\n=== Test Results ===");

    $display("Total tests: %0d", test_count + 3); // +3 for RX-only tests

    $display("Errors: %0d", error_count);

    if (error_count == 0) begin

    $display("✓ ALL TESTS PASSED!");

    end else begin

    $display("✗ %0d TESTS FAILED!", error_count);

    end

    $display("\n=== UART Testbench Complete ===");

    $finish;

    end

    // Timeout watchdog

    initial begin

    #(BIT_PERIOD * 1000); // Long timeout

    $display("ERROR: Testbench timeout!");

    $finish;

    end

    // Optional: Dump waveforms

    initial begin

    $dumpfile("uart_tb.vcd");

    $dumpvars(0, tb_uart_top);

    end


    endmodule


  • KennyT_altera's avatar
    KennyT_altera
    Icon for Super Contributor rankSuper Contributor

    Do you have any further questions? If not, we’ll go ahead and close this thread.

    By the way, please note that we do not provide support for customized code. The code shared above is based on open-source examples and is provided as-is.


    Thanks


  • KennyT_altera's avatar
    KennyT_altera
    Icon for Super Contributor rankSuper Contributor

    As we do not receive any response from you on the previous answer that we have provided. Please login to ‘https://supporttickets.intel.com/s/?language=en_US’, view details of the desire request, and post a feed/response within the next 15 days to allow me to continue to support you. After 15 days, this thread will be transitioned to community support. The community users will be able to help you on your follow-up questions.