======The CharDrawer Module======
This module is used to draw strings of characters to the display, one pixel at a time. The module accepts only upper case characters (A-Z), digits (0-9) and space (" "). A null character (0) will not be drawn, and all other ASCII values are drawn as a solid rectangle.
There are several different ways to construct strings in SystemVerilog. For example, the following are all equivalent:
logic [39:0] char_string;
assign char_string = "HELLO";
logic [39:0] char_string;
assign char_string = 40'h48454C4C4F;
logic [39:0] char_string;
assign char_string = {"H", "E", "L", "L", "O"};
logic [39:0] char_string;
assign char_string = {8'h48, 8'h45, 8'h4C, 8'h4C, 8'h4F};
^ Module Name = CharDrawer ^^^^
^ Parameter ^ Default Value ^ Description ^^
| MAX_CHARS | 16 |The length of the string that the module can display. ||
^ Port Name ^ Direction ^ Width ^ Description ^
| clk | Input | 1 | 100 MHz Clock |
| reset | Input | 1 | Active-high reset |
| enable| Input | 1 | Raise this signal to start drawing. The drawing will continue until finished. To draw a new string you must lower and then raise this signal. |
| done | Output | 1 | Active-high, indicating that the string is done drawing |
| x_in | Input | 9 | Top-left x-coordinate of drawing region |
| y_in | Input | 8 | Top-left y-coordinate of drawing region |
| string_in | Input | MAX_CHARS * 8 | ASCII character string to draw, most-significant byte is drawn first. |
| x_out | Output| 9 | x-Coordinate of pixel to draw |
| y_out| Output | 8 | y-Coordinate of pixel to draw |
Click the link below to download the CharDrawer.sv file.
module CharDrawer # (
parameter MAX_CHARS = 16
)(
input wire logic clk, // Clock
input wire logic reset, // Active-high reset
input wire logic enable, // Start drawing
output logic done, // Done drawing
input wire logic [8:0] x_in, // Top-left (x,y)
input wire logic [7:0] y_in,
input wire logic [(MAX_CHARS * 8) - 1:0] string_in, // ASCII character string to draw
// MSB is drawn first
output logic [8:0] x_out, // Output (x,y) of pixel to draw
output logic [7:0] y_out,
output logic draw_en // Active-high enable of drawing
);
localparam CHAR_COLS = 5;
localparam CHAR_ROWS = 5;
logic [8:0] x; // Track top-left (x,y) of current character to draw
logic [7:0] y;
// ROM to lookup pixel map of each character, row by row
logic [8:0] pixel_rom_addr; // { 5 bits of char_id, see table below, 3 bits for row #}
logic [4:0] pixel_rom_out; // row of pixel data, output from ROM
logic [4:0] pixel_row; // Shift register populated with row of pixel data, then shifted out as it is drawn
// ROM to lookup width of each character
logic [5:0] width_rom_addr; // char_id
logic [2:0] width_rom_out; // width of character
logic [2:0] char_width; // width of character, saved to register
// char_id
// -------
// 0-25 = A-Z
// 26-35 = 0-9
// 36 = " " (space)
// 37 = all unsported characters, except null (0) which is not printed
logic [7:0] char_idx; // Index of character being drawn, starts at MAX_CHARS-1, decrements to 0
logic [2:0] col_idx; // Current column being drawn
logic [2:0] row_idx; // Current row being drawn
logic col_done; // High when on the last column of the row
logic row_done; // High when on the last row of the character
logic char_done; // High when on the last row and col
// State Machine
typedef enum {S_INIT, S_NEXT_CHAR, S_READ_ROW, S_SAVE_ROW, S_DRAW_ROW, S_DONE} StateType;
StateType cs;
logic char_is_null;
logic [7:0] curr_char;
logic [7:0] char_select;
////////////////////////////////// Output Logic ////////////////////////////////////
assign x_out = x + col_idx;
assign y_out = y + row_idx;
assign draw_en = pixel_row[CHAR_COLS - 1];
assign done = (char_done && char_idx == 0) || (cs == S_NEXT_CHAR && char_is_null && char_idx == 0);
////////////////////////////////// State Machine //////////////////////////////////
always_ff @(posedge clk) begin
if (reset) begin
cs <= S_INIT;
end else begin
case (cs)
S_INIT:
if (enable)
cs <= S_NEXT_CHAR;
S_NEXT_CHAR:
if (~char_is_null)
cs <= S_READ_ROW;
else if (char_idx == 0)
cs <= S_DONE;
S_READ_ROW:
cs <= S_SAVE_ROW;
S_SAVE_ROW:
cs <= S_DRAW_ROW;
S_DRAW_ROW: begin
if (col_done) begin
if (row_done) begin
if (char_idx == 0) begin
cs <= S_DONE;
end else begin
cs <= S_NEXT_CHAR;
end
end else begin
cs <= S_READ_ROW;
end
end
end
S_DONE:
if (!enable)
cs <= S_INIT;
endcase
end
end
////////////////////////////////// Row/Col/Char Counting //////////////////////////////////
assign col_done = (cs == S_DRAW_ROW) && (col_idx == (char_width - 1));
assign row_done = (cs == S_DRAW_ROW) && (row_idx == (CHAR_ROWS - 1));
assign char_done = col_done && row_done;
assign char_select = string_in[char_idx * 8 +: 8];
assign char_is_null = (char_select == 0);
always_ff @(posedge clk) begin
if (cs == S_NEXT_CHAR)
curr_char <= char_select;
end
// char_idx
always_ff @(posedge clk) begin
if (cs == S_INIT)
char_idx <= MAX_CHARS - 1;
else if (char_done)
char_idx <= char_idx - 1;
else if (cs == S_NEXT_CHAR && char_is_null)
char_idx <= char_idx - 1;
end
// col_idx
always_ff @(posedge clk) begin
if (cs == S_READ_ROW)
col_idx <= 0;
else if (cs == S_DRAW_ROW)
col_idx <= col_idx + 1;
end
// row_idx
always_ff @(posedge clk) begin
if (cs == S_NEXT_CHAR)
row_idx <= 0;
if (col_done)
row_idx <= row_idx + 1;
end
// x,y
always_ff @(posedge clk) begin
if (cs == S_INIT) begin
x <= x_in;
y <= y_in;
end else if (char_done) begin
x <= x + char_width + 1;
end
end
// Shift register of current row of pixels being drawn
always_ff @(posedge clk) begin
if (cs == S_INIT)
pixel_row <= 0;
if (cs == S_SAVE_ROW)
pixel_row <= pixel_rom_out;
else if (cs == S_DRAW_ROW)
pixel_row <= {pixel_row[CHAR_COLS - 2:0], 1'b0};
end
////////////////////////////////// Character ROM Lookups //////////////////////////////////
always_comb begin
if (curr_char >= "A" && curr_char <= "Z") begin
pixel_rom_addr = {curr_char - "A", row_idx};
width_rom_addr = {curr_char - "A"};
end else if (curr_char >= "0" && curr_char <= "9") begin
pixel_rom_addr = {curr_char - "0" + 26, row_idx};
width_rom_addr = {curr_char - "0" + 26};
end else if (curr_char == " ") begin
pixel_rom_addr = {8'd36, row_idx};
width_rom_addr = 8'd36;
end else begin
pixel_rom_addr = {8'd37, row_idx};
width_rom_addr = 8'd37;
end
end
always_ff @(posedge clk) begin
if (cs == S_SAVE_ROW)
char_width <= width_rom_out;
end
always_ff @(posedge clk) begin
case(pixel_rom_addr)
{"A" - "A", 3'd0}: pixel_rom_out = 5'b11100;
{"A" - "A", 3'd1}: pixel_rom_out = 5'b10100;
{"A" - "A", 3'd2}: pixel_rom_out = 5'b11100;
{"A" - "A", 3'd3}: pixel_rom_out = 5'b10100;
{"A" - "A", 3'd4}: pixel_rom_out = 5'b10100;
{"B" - "A", 3'd0}: pixel_rom_out = 5'b11100;
{"B" - "A", 3'd1}: pixel_rom_out = 5'b10100;
{"B" - "A", 3'd2}: pixel_rom_out = 5'b11110;
{"B" - "A", 3'd3}: pixel_rom_out = 5'b10010;
{"B" - "A", 3'd4}: pixel_rom_out = 5'b11110;
{"C" - "A", 3'd0}: pixel_rom_out = 5'b11100;
{"C" - "A", 3'd1}: pixel_rom_out = 5'b10000;
{"C" - "A", 3'd2}: pixel_rom_out = 5'b10000;
{"C" - "A", 3'd3}: pixel_rom_out = 5'b10000;
{"C" - "A", 3'd4}: pixel_rom_out = 5'b11100;
{"D" - "A", 3'd0}: pixel_rom_out = 5'b11000;
{"D" - "A", 3'd1}: pixel_rom_out = 5'b10100;
{"D" - "A", 3'd2}: pixel_rom_out = 5'b10100;
{"D" - "A", 3'd3}: pixel_rom_out = 5'b10100;
{"D" - "A", 3'd4}: pixel_rom_out = 5'b11000;
{"E" - "A", 3'd0}: pixel_rom_out = 5'b11100;
{"E" - "A", 3'd1}: pixel_rom_out = 5'b10000;
{"E" - "A", 3'd2}: pixel_rom_out = 5'b11000;
{"E" - "A", 3'd3}: pixel_rom_out = 5'b10000;
{"E" - "A", 3'd4}: pixel_rom_out = 5'b11100;
{"F" - "A", 3'd0}: pixel_rom_out = 5'b11100;
{"F" - "A", 3'd1}: pixel_rom_out = 5'b10000;
{"F" - "A", 3'd2}: pixel_rom_out = 5'b11100;
{"F" - "A", 3'd3}: pixel_rom_out = 5'b10000;
{"F" - "A", 3'd4}: pixel_rom_out = 5'b10000;
{"G" - "A", 3'd0}: pixel_rom_out = 5'b11110;
{"G" - "A", 3'd1}: pixel_rom_out = 5'b10000;
{"G" - "A", 3'd2}: pixel_rom_out = 5'b10110;
{"G" - "A", 3'd3}: pixel_rom_out = 5'b10010;
{"G" - "A", 3'd4}: pixel_rom_out = 5'b11110;
{"H" - "A", 3'd0}: pixel_rom_out = 5'b10100;
{"H" - "A", 3'd1}: pixel_rom_out = 5'b10100;
{"H" - "A", 3'd2}: pixel_rom_out = 5'b11100;
{"H" - "A", 3'd3}: pixel_rom_out = 5'b10100;
{"H" - "A", 3'd4}: pixel_rom_out = 5'b10100;
{"I" - "A", 3'd0}: pixel_rom_out = 5'b10000;
{"I" - "A", 3'd1}: pixel_rom_out = 5'b10000;
{"I" - "A", 3'd2}: pixel_rom_out = 5'b10000;
{"I" - "A", 3'd3}: pixel_rom_out = 5'b10000;
{"I" - "A", 3'd4}: pixel_rom_out = 5'b10000;
{"J" - "A", 3'd0}: pixel_rom_out = 5'b11100;
{"J" - "A", 3'd1}: pixel_rom_out = 5'b00100;
{"J" - "A", 3'd2}: pixel_rom_out = 5'b00100;
{"J" - "A", 3'd3}: pixel_rom_out = 5'b10100;
{"J" - "A", 3'd4}: pixel_rom_out = 5'b11100;
{"K" - "A", 3'd0}: pixel_rom_out = 5'b10100;
{"K" - "A", 3'd1}: pixel_rom_out = 5'b10100;
{"K" - "A", 3'd2}: pixel_rom_out = 5'b11000;
{"K" - "A", 3'd3}: pixel_rom_out = 5'b10100;
{"K" - "A", 3'd4}: pixel_rom_out = 5'b10100;
{"L" - "A", 3'd0}: pixel_rom_out = 5'b10000;
{"L" - "A", 3'd1}: pixel_rom_out = 5'b10000;
{"L" - "A", 3'd2}: pixel_rom_out = 5'b10000;
{"L" - "A", 3'd3}: pixel_rom_out = 5'b10000;
{"L" - "A", 3'd4}: pixel_rom_out = 5'b11100;
{"M" - "A", 3'd0}: pixel_rom_out = 5'b10001;
{"M" - "A", 3'd1}: pixel_rom_out = 5'b11011;
{"M" - "A", 3'd2}: pixel_rom_out = 5'b10101;
{"M" - "A", 3'd3}: pixel_rom_out = 5'b10001;
{"M" - "A", 3'd4}: pixel_rom_out = 5'b10001;
{"N" - "A", 3'd0}: pixel_rom_out = 5'b10010;
{"N" - "A", 3'd1}: pixel_rom_out = 5'b11010;
{"N" - "A", 3'd2}: pixel_rom_out = 5'b10110;
{"N" - "A", 3'd3}: pixel_rom_out = 5'b10010;
{"N" - "A", 3'd4}: pixel_rom_out = 5'b10010;
{"O" - "A", 3'd0}: pixel_rom_out = 5'b11100;
{"O" - "A", 3'd1}: pixel_rom_out = 5'b10100;
{"O" - "A", 3'd2}: pixel_rom_out = 5'b10100;
{"O" - "A", 3'd3}: pixel_rom_out = 5'b10100;
{"O" - "A", 3'd4}: pixel_rom_out = 5'b11100;
{"P" - "A", 3'd0}: pixel_rom_out = 5'b11100;
{"P" - "A", 3'd1}: pixel_rom_out = 5'b10100;
{"P" - "A", 3'd2}: pixel_rom_out = 5'b11100;
{"P" - "A", 3'd3}: pixel_rom_out = 5'b10000;
{"P" - "A", 3'd4}: pixel_rom_out = 5'b10000;
{"Q" - "A", 3'd0}: pixel_rom_out = 5'b11100;
{"Q" - "A", 3'd1}: pixel_rom_out = 5'b10100;
{"Q" - "A", 3'd2}: pixel_rom_out = 5'b10100;
{"Q" - "A", 3'd3}: pixel_rom_out = 5'b10100;
{"Q" - "A", 3'd4}: pixel_rom_out = 5'b11110;
{"R" - "A", 3'd0}: pixel_rom_out = 5'b11100;
{"R" - "A", 3'd1}: pixel_rom_out = 5'b10100;
{"R" - "A", 3'd2}: pixel_rom_out = 5'b11000;
{"R" - "A", 3'd3}: pixel_rom_out = 5'b10100;
{"R" - "A", 3'd4}: pixel_rom_out = 5'b10100;
{"S" - "A", 3'd0}: pixel_rom_out = 5'b11100;
{"S" - "A", 3'd1}: pixel_rom_out = 5'b10000;
{"S" - "A", 3'd2}: pixel_rom_out = 5'b11100;
{"S" - "A", 3'd3}: pixel_rom_out = 5'b00100;
{"S" - "A", 3'd4}: pixel_rom_out = 5'b11100;
{"T" - "A", 3'd0}: pixel_rom_out = 5'b11100;
{"T" - "A", 3'd1}: pixel_rom_out = 5'b01000;
{"T" - "A", 3'd2}: pixel_rom_out = 5'b01000;
{"T" - "A", 3'd3}: pixel_rom_out = 5'b01000;
{"T" - "A", 3'd4}: pixel_rom_out = 5'b01000;
{"U" - "A", 3'd0}: pixel_rom_out = 5'b10100;
{"U" - "A", 3'd1}: pixel_rom_out = 5'b10100;
{"U" - "A", 3'd2}: pixel_rom_out = 5'b10100;
{"U" - "A", 3'd3}: pixel_rom_out = 5'b10100;
{"U" - "A", 3'd4}: pixel_rom_out = 5'b11100;
{"V" - "A", 3'd0}: pixel_rom_out = 5'b10100;
{"V" - "A", 3'd1}: pixel_rom_out = 5'b10100;
{"V" - "A", 3'd2}: pixel_rom_out = 5'b10100;
{"V" - "A", 3'd3}: pixel_rom_out = 5'b11100;
{"V" - "A", 3'd4}: pixel_rom_out = 5'b01000;
{"W" - "A", 3'd0}: pixel_rom_out = 5'b10001;
{"W" - "A", 3'd1}: pixel_rom_out = 5'b10001;
{"W" - "A", 3'd2}: pixel_rom_out = 5'b10101;
{"W" - "A", 3'd3}: pixel_rom_out = 5'b11011;
{"W" - "A", 3'd4}: pixel_rom_out = 5'b10001;
{"X" - "A", 3'd0}: pixel_rom_out = 5'b10100;
{"X" - "A", 3'd1}: pixel_rom_out = 5'b10100;
{"X" - "A", 3'd2}: pixel_rom_out = 5'b01000;
{"X" - "A", 3'd3}: pixel_rom_out = 5'b10100;
{"X" - "A", 3'd4}: pixel_rom_out = 5'b10100;
{"Y" - "A", 3'd0}: pixel_rom_out = 5'b10100;
{"Y" - "A", 3'd1}: pixel_rom_out = 5'b10100;
{"Y" - "A", 3'd2}: pixel_rom_out = 5'b11100;
{"Y" - "A", 3'd3}: pixel_rom_out = 5'b01000;
{"Y" - "A", 3'd4}: pixel_rom_out = 5'b01000;
{"Z" - "A", 3'd0}: pixel_rom_out = 5'b11100;
{"Z" - "A", 3'd1}: pixel_rom_out = 5'b00100;
{"Z" - "A", 3'd2}: pixel_rom_out = 5'b01000;
{"Z" - "A", 3'd3}: pixel_rom_out = 5'b10000;
{"Z" - "A", 3'd4}: pixel_rom_out = 5'b11100;
{"0" - "0" + 26, 3'd0}: pixel_rom_out = 5'b11100;
{"0" - "0" + 26, 3'd1}: pixel_rom_out = 5'b10100;
{"0" - "0" + 26, 3'd2}: pixel_rom_out = 5'b10100;
{"0" - "0" + 26, 3'd3}: pixel_rom_out = 5'b10100;
{"0" - "0" + 26, 3'd4}: pixel_rom_out = 5'b11100;
{"1" - "0" + 26, 3'd0}: pixel_rom_out = 5'b11000;
{"1" - "0" + 26, 3'd1}: pixel_rom_out = 5'b01000;
{"1" - "0" + 26, 3'd2}: pixel_rom_out = 5'b01000;
{"1" - "0" + 26, 3'd3}: pixel_rom_out = 5'b01000;
{"1" - "0" + 26, 3'd4}: pixel_rom_out = 5'b01000;
{"2" - "0" + 26, 3'd0}: pixel_rom_out = 5'b11100;
{"2" - "0" + 26, 3'd1}: pixel_rom_out = 5'b00100;
{"2" - "0" + 26, 3'd2}: pixel_rom_out = 5'b11100;
{"2" - "0" + 26, 3'd3}: pixel_rom_out = 5'b10000;
{"2" - "0" + 26, 3'd4}: pixel_rom_out = 5'b11100;
{"3" - "0" + 26, 3'd0}: pixel_rom_out = 5'b11100;
{"3" - "0" + 26, 3'd1}: pixel_rom_out = 5'b00100;
{"3" - "0" + 26, 3'd2}: pixel_rom_out = 5'b01100;
{"3" - "0" + 26, 3'd3}: pixel_rom_out = 5'b00100;
{"3" - "0" + 26, 3'd4}: pixel_rom_out = 5'b11100;
{"4" - "0" + 26, 3'd0}: pixel_rom_out = 5'b10100;
{"4" - "0" + 26, 3'd1}: pixel_rom_out = 5'b10100;
{"4" - "0" + 26, 3'd2}: pixel_rom_out = 5'b11100;
{"4" - "0" + 26, 3'd3}: pixel_rom_out = 5'b00100;
{"4" - "0" + 26, 3'd4}: pixel_rom_out = 5'b00100;
{"5" - "0" + 26, 3'd0}: pixel_rom_out = 5'b11100;
{"5" - "0" + 26, 3'd1}: pixel_rom_out = 5'b10000;
{"5" - "0" + 26, 3'd2}: pixel_rom_out = 5'b11100;
{"5" - "0" + 26, 3'd3}: pixel_rom_out = 5'b00100;
{"5" - "0" + 26, 3'd4}: pixel_rom_out = 5'b11100;
{"6" - "0" + 26, 3'd0}: pixel_rom_out = 5'b10000;
{"6" - "0" + 26, 3'd1}: pixel_rom_out = 5'b10000;
{"6" - "0" + 26, 3'd2}: pixel_rom_out = 5'b11100;
{"6" - "0" + 26, 3'd3}: pixel_rom_out = 5'b10100;
{"6" - "0" + 26, 3'd4}: pixel_rom_out = 5'b11100;
{"7" - "0" + 26, 3'd0}: pixel_rom_out = 5'b11100;
{"7" - "0" + 26, 3'd1}: pixel_rom_out = 5'b00100;
{"7" - "0" + 26, 3'd2}: pixel_rom_out = 5'b00100;
{"7" - "0" + 26, 3'd3}: pixel_rom_out = 5'b00100;
{"7" - "0" + 26, 3'd4}: pixel_rom_out = 5'b00100;
{"8" - "0" + 26, 3'd0}: pixel_rom_out = 5'b11100;
{"8" - "0" + 26, 3'd1}: pixel_rom_out = 5'b10100;
{"8" - "0" + 26, 3'd2}: pixel_rom_out = 5'b11100;
{"8" - "0" + 26, 3'd3}: pixel_rom_out = 5'b10100;
{"8" - "0" + 26, 3'd4}: pixel_rom_out = 5'b11100;
{"9" - "0" + 26, 3'd0}: pixel_rom_out = 5'b11100;
{"9" - "0" + 26, 3'd1}: pixel_rom_out = 5'b10100;
{"9" - "0" + 26, 3'd2}: pixel_rom_out = 5'b11100;
{"9" - "0" + 26, 3'd3}: pixel_rom_out = 5'b00100;
{"9" - "0" + 26, 3'd4}: pixel_rom_out = 5'b00100;
{8'd36, 3'd0}: pixel_rom_out = 5'b00000;
{8'd36, 3'd1}: pixel_rom_out = 5'b00000;
{8'd36, 3'd2}: pixel_rom_out = 5'b00000;
{8'd36, 3'd3}: pixel_rom_out = 5'b00000;
{8'd36, 3'd4}: pixel_rom_out = 5'b00000;
{8'd37, 3'd0}: pixel_rom_out = 5'b11110;
{8'd37, 3'd1}: pixel_rom_out = 5'b11110;
{8'd37, 3'd2}: pixel_rom_out = 5'b11110;
{8'd37, 3'd3}: pixel_rom_out = 5'b11110;
{8'd37, 3'd4}: pixel_rom_out = 5'b11110;
endcase
end
always_ff @(posedge clk) begin
case(width_rom_addr)
// A-Z
{"A" - "A"}: width_rom_out = 3;
{"B" - "A"}: width_rom_out = 4;
{"C" - "A"}: width_rom_out = 3;
{"D" - "A"}: width_rom_out = 3;
{"E" - "A"}: width_rom_out = 3;
{"F" - "A"}: width_rom_out = 3;
{"G" - "A"}: width_rom_out = 4;
{"H" - "A"}: width_rom_out = 3;
{"I" - "A"}: width_rom_out = 1;
{"J" - "A"}: width_rom_out = 3;
{"K" - "A"}: width_rom_out = 3;
{"L" - "A"}: width_rom_out = 3;
{"M" - "A"}: width_rom_out = 5;
{"N" - "A"}: width_rom_out = 4;
{"O" - "A"}: width_rom_out = 3;
{"P" - "A"}: width_rom_out = 3;
{"Q" - "A"}: width_rom_out = 4;
{"R" - "A"}: width_rom_out = 3;
{"S" - "A"}: width_rom_out = 3;
{"T" - "A"}: width_rom_out = 3;
{"U" - "A"}: width_rom_out = 3;
{"V" - "A"}: width_rom_out = 3;
{"W" - "A"}: width_rom_out = 5;
{"X" - "A"}: width_rom_out = 3;
{"Y" - "A"}: width_rom_out = 3;
{"Z" - "A"}: width_rom_out = 3;
// 0-9
{"0" - "0" + 26}: width_rom_out = 3;
{"1" - "0" + 26}: width_rom_out = 2;
{"2" - "0" + 26}: width_rom_out = 3;
{"3" - "0" + 26}: width_rom_out = 3;
{"4" - "0" + 26}: width_rom_out = 3;
{"5" - "0" + 26}: width_rom_out = 3;
{"6" - "0" + 26}: width_rom_out = 3;
{"7" - "0" + 26}: width_rom_out = 3;
{"8" - "0" + 26}: width_rom_out = 3;
{"9" - "0" + 26}: width_rom_out = 3;
// Space
36: width_rom_out = 4;
// non-supported, non-null
37: width_rom_out = 4;
endcase
end
endmodule