FPGA LCD Display Implementation: Character and Image Rendering

FPGA LCD Display Implementation: Character and Image Rendering

Character and Image Display Fundamentals

In FPGA-based LCD display systems, character and image rendering requires understanding pixel matrix representation. A character can be represented as a 2D array where each element corresponds to a pixel point. For example, char[y][x] denotes the pixel at row y and column x. Images and text can be converted into matrix arrays using specialized tools.

For implementing a 16x16 character, we would declare a register array as reg [15:0] char [15:0], creating a matrix with 16 rows and 16 columns.

Coordinate Calculation and Timing Analysis

When implementing LCD display logic, we need to calculate pixel coordinates relative to character regions. The following Verilog code demonstrates how to compute horizontal and vertical coordinates relative to character starting positions:

assign h_coord = pixel_x + 1'b1 - CHAR_H_START; // Horizontal coordinate relative to character region
assign v_coord = pixel_y - CHAR_V_START; // Vertical coordinate relative to character region
assign rom_enable = 1'b1; // Enable constant ROM reading

Multi-Region Display Implementation

The following Verilog module implements a display system capable of rendering images, characters, and background colors in different screen regions. This implementation accounts for timing delays inherent in LCD display systems.

always @(posedge lcd_clk or negedge reset_n) begin
    if (!reset_n)
        pixel_output <= DEFAULT_BG;
    else if( (pixel_x >= IMG_H_START - 1'b1) && (pixel_x < IMG_H_START + IMG_WIDTH - 1'b1) 
          && (pixel_y >= IMG_V_START) && (pixel_y < IMG_V_START + IMG_HEIGHT) )
        pixel_output <= rom_data;  // Display image
    else if((pixel_x >= TEXT_H_START - 1'b1) && (pixel_x < TEXT_H_START + TEXT_WIDTH - 1'b1)
         && (pixel_y >= TEXT_V_START) && (pixel_y < TEXT_V_START + TEXT_HEIGHT)) begin
        if(text_matrix[v_coord][TEXT_WIDTH -1'b1 - h_coord])
            pixel_output <= TEXT_COLOR;    // Display character
        else
            pixel_output <= DEFAULT_BG;    // Character background
    end
    else
        pixel_output <= DEFAULT_BG;        // Screen background
end

ROM Address Management

Proper ROM address management is crucial for image display. The following code increments the ROM address when scanning within the image region and resets it when moving to the next frame:

always @(posedge lcd_clk or negedge reset_n) begin
    if(!reset_n)
        rom_address <= 14'd0;
    // When coordinates are within image display area, increment ROM address    
    else if((pixel_y >= IMG_V_START) && (pixel_y < IMG_V_START + IMG_HEIGHT) 
        && (pixel_x >= IMG_H_START - 2'd2) && (pixel_x < IMG_H_START + IMG_WIDTH - 2'd2)) 
        rom_address <= rom_address + 1'b1;
    // When at the last pixel of image area, reset ROM address    
    else if((pixel_y >= IMG_V_START + IMG_HEIGHT))
        rom_address <= 14'd0;
end

Complete Display Module

module lcd_renderer(
    input             lcd_clk,      // Clock signal
    input             reset_n,      // Active-low reset
                                    
    input      [10:0] pixel_x,     // Horizontal pixel position
    input      [10:0] pixel_y,     // Vertical pixel position    
    output reg [23:0] pixel_output // Pixel color data
);                                   
                                     
// Parameter definitions                   
localparam IMG_H_START = 11'd10;     // Image horizontal start position
localparam IMG_V_START = 11'd10;     // Image vertical start position
localparam IMG_WIDTH   = 11'd100;    // Image width
localparam IMG_HEIGHT  = 11'd100;    // Image height
                       
localparam TEXT_H_START= 11'd10;     // Text horizontal start position
localparam TEXT_V_START= 11'd120;    // Text vertical start position
localparam TEXT_WIDTH  = 11'd128;    // Text width
localparam TEXT_HEIGHT = 11'd32;     // Text height
                       
localparam DEFAULT_BG  = 24'hE0FFFF; // Background color (light blue)
localparam TEXT_COLOR  = 24'hff0000; // Text color (red)

// Register declarations
reg   [127:0] text_matrix[31:0];  // Character matrix
reg   [13:0]  rom_address ;  // ROM address

// Wire declarations   
wire  [10:0]  h_coord;       // Horizontal coordinate counter
wire  [10:0]  v_coord;       // Vertical coordinate counter
wire          rom_enable ;   // ROM read enable signal
wire  [23:0]  rom_data ;     // ROM output data

assign  h_coord = pixel_x + 1'b1  - TEXT_H_START; // Horizontal coordinate relative to text region
assign  v_coord = pixel_y - TEXT_V_START; // Vertical coordinate relative to text region
assign  rom_enable = 1'b1;                  // Constant ROM read enable

always @(posedge lcd_clk) begin
    text_matrix[0 ]  <= 128'h00000000000000000000000000000000;
    text_matrix[1 ]  <= 128'h00000000000000000000000000000000;
    text_matrix[2 ]  <= 128'h00000000000100000000002000000000;
    text_matrix[3 ]  <= 128'h000000100001800002000070000000C0;
    text_matrix[4 ]  <= 128'h000000380001800003FFFFF803FFFFE0;
    text_matrix[5 ]  <= 128'h07FFFFFC0001800003006000000001E0;
    text_matrix[6 ]  <= 128'h0000C000000180600300600000000300;
    text_matrix[7 ]  <= 128'h0000C0000001FFF00300C00000000600;
    text_matrix[8 ]  <= 128'h0000C000000180000310804000001800;
    text_matrix[9 ]  <= 128'h0000C00000018000031FFFE000003000;
    text_matrix[10]  <= 128'h0000C00000018000031800400001C000;
    text_matrix[11]  <= 128'h0000C00000018000031800400001C000;
    text_matrix[12]  <= 128'h00C0C000018181800318004000018000;
    text_matrix[13]  <= 128'h00C0C00001FFFFC0031FFFC000018010;
    text_matrix[14]  <= 128'h00C0C060018001800318004000018038;
    text_matrix[15]  <= 128'h00C0FFF001800180031800403FFFFFFC;
    text_matrix[16]  <= 128'h00C0C000018001800318004000018000;
    text_matrix[17]  <= 128'h00C0C000018001800218004000018000;
    text_matrix[18]  <= 128'h00C0C00001800180021FFFC000018000;
    text_matrix[19]  <= 128'h00C0C000018001800210304000018000;
    text_matrix[20]  <= 128'h00C0C00001FFFF800200300000018000;
    text_matrix[21]  <= 128'h00C0C000018001800606300000018000;
    text_matrix[22]  <= 128'h00C0C000018001000607370000018000;
    text_matrix[23]  <= 128'h00C0C00000000000060E31C000018000;
    text_matrix[24]  <= 128'h00C0C000001000400418307000018000;
    text_matrix[25]  <= 128'h00C0C000020830600430303800018000;
    text_matrix[26]  <= 128'h00C0C010020C18300860301800018000;
    text_matrix[27]  <= 128'h00C0C038060E18180883700800018000;
    text_matrix[28]  <= 128'h3FFFFFFC0C0618181100F008003F8000;
    text_matrix[29]  <= 128'h000000001C0408182000600000070000;
    text_matrix[30]  <= 128'h00000000000000000000000000020000;
    text_matrix[31]  <= 128'h00000000000000000000000000000000;
end

// Render different display regions (image, text, background)
always @(posedge lcd_clk or negedge reset_n) begin
    if(!reset_n)
        pixel_output <= DEFAULT_BG;
    else if((pixel_x >= IMG_H_START - 1'b1) && (pixel_x < IMG_H_START + IMG_WIDTH - 1'b1)
            && (pixel_y >= IMG_V_START) && (pixel_y < IMG_V_START + IMG_HEIGHT))
        pixel_output <= rom_data;   // Image rendering
    else if((pixel_x >= TEXT_H_START - 1'b1) && (pixel_x < TEXT_H_START + TEXT_WIDTH - 1'b1)
            && (pixel_y >= TEXT_V_START) && (pixel_y < TEXT_V_START + TEXT_HEIGHT))begin
            if(text_matrix[v_coord][TEXT_WIDTH - 1'b1 - h_coord])    // Text rendering
                pixel_output <= TEXT_COLOR;   // Text color (red)
            else
                pixel_output <= DEFAULT_BG;   // Background color (light blue)
    end
    else
        pixel_output <= DEFAULT_BG;
end

// Manage ROM address based on current scanning position
always @(posedge lcd_clk or negedge reset_n) begin
    if(!reset_n)
        rom_address <= 14'd0;
    else if((pixel_y >= IMG_V_START) && (pixel_y < IMG_V_START + IMG_HEIGHT)
            && (pixel_x >= IMG_H_START - 2'd2) && (pixel_x < IMG_H_START + IMG_WIDTH - 2'd2))
        rom_address <= rom_address + 1'b1;
    else if(pixel_y >= IMG_V_START + IMG_HEIGHT)
        rom_address <= 14'd0;
    else
        rom_address <= rom_address;
end

// Block memory for image storage
image_rom rom_inst (
  .clk  (lcd_clk),    // Clock input
  .en   (rom_enable),   // Enable input
  .addr (rom_address),    // Address input [13:0]
  .dout (rom_data)  // Data output [23:0]
);
endmodule

Tags: FPGA LCD Display Verilog Character Rendering Image Display

Posted on Wed, 22 Jul 2026 16:19:08 +0000 by Megahertza