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