5
\$\begingroup\$

I'm an ECE student. My experience in Verilog and FPGAs is mainly from my digital logic design class. To practice Verilog, I decided to implement a controller for Adafruit LED matrices. It interfaces with a single-port BRAM to access pixel data.

If helpful, here is an overview of how it works:

The design has an FSM that alternates between the DISPLAY_1 and DISPLAY_2 states to read pixel data and generate an address for the BRAM each clock cycle. In the DISPLAY_2 state, it writes the pixel data to registers connected to the o_color1 and o_color2 output signals. Since this design drives the clock (o_clk) for the LED matrix, I ensure that these output signals only change on the falling edge of o_clk. After driving the data for all the pixels in a row, it increments the o_row_sel register to select the next two rows.

led_matrix_controller.v:

`timescale 1ns / 1ps
module led_matrix_controller 
 #(parameter MATRIX_COLS = 64,
 parameter MATRIX_ROWS = 32,
 parameter PWM_BITS = 1
 )
 (input i_clk,
 input rst,
 input [(3*PWM_BITS)-1:0] i_pixel_data,
 output reg [$clog2(MATRIX_COLS*MATRIX_ROWS)-1:0] o_pixel_addr,
 output reg o_clk,
 output reg o_oe,
 output reg o_latch,
 output reg [$clog2(MATRIX_ROWS/2)-1:0] o_row_sel,
 output [2:0] o_color1,
 output [2:0] o_color2
 );
 wire [$clog2(MATRIX_ROWS/2)-1:0] o_row_sel_next = o_row_sel + 1;
 reg [$clog2(MATRIX_COLS):0] pixel_counter;
 reg [PWM_BITS-1:0] pwm_counter;
 reg first_cycle;
 reg [(3*PWM_BITS)-1:0] pixel_1;
 reg [(3*PWM_BITS)-1:0] pixel_2;
 reg [(3*PWM_BITS)-1:0] temp_pixel;
 assign o_color1 = color(pixel_1);
 assign o_color2 = color(pixel_2);
 function [2:0] color (input reg [(3*PWM_BITS)-1:0] pixel);
 color = {
 (pwm_counter < pixel[PWM_BITS-1:0]),
 (pwm_counter < pixel[(2*PWM_BITS)-1:PWM_BITS]),
 (pwm_counter < pixel[(3*PWM_BITS)-1:2*PWM_BITS])
 };
 endfunction
 reg [2:0] state;
 localparam STATE_DISPLAY_1 = 3'b000;
 localparam STATE_DISPLAY_2 = 3'b001;
 localparam STATE_BLANK_1 = 3'b010;
 localparam STATE_BLANK_2 = 3'b011;
 localparam STATE_BLANK_3 = 3'b100;
 always @(posedge i_clk)
 begin
 if(rst)
 begin
 o_clk <= 0;
 o_oe <= 0;
 o_latch <= 0;
 o_row_sel <= ~0;
 o_pixel_addr <= 0;
 
 pixel_1 <= 0;
 pixel_2 <= 0;
 temp_pixel <= 0;
 
 pixel_counter <= 0;
 pwm_counter <= 0;
 first_cycle <= 1;
 
 state <= STATE_DISPLAY_2;
 end
 else
 begin
 case(state)
 STATE_DISPLAY_1:
 begin
 o_oe <= 0;
 o_latch <= 0;
 o_clk <= !first_cycle;
 temp_pixel <= i_pixel_data;
 if(pixel_counter > MATRIX_COLS)
 begin
 state <= STATE_BLANK_1;
 end
 else
 begin
 o_pixel_addr <= pixel_counter + MATRIX_COLS*o_row_sel_next;
 state <= STATE_DISPLAY_2;
 end
 end
 STATE_DISPLAY_2:
 begin
 o_clk <= 0;
 first_cycle <= 0;
 if(!first_cycle)
 begin
 pixel_1 <= temp_pixel;
 pixel_2 <= i_pixel_data;
 end
 o_pixel_addr <= pixel_counter + MATRIX_COLS*(MATRIX_ROWS/2 + o_row_sel_next);
 pixel_counter <= pixel_counter + 1;
 state <= STATE_DISPLAY_1;
 end
 STATE_BLANK_1:
 begin
 if(o_pixel_addr != 0)
 begin
 pixel_1 <= temp_pixel;
 pixel_2 <= i_pixel_data;
 end
 else
 begin
 pixel_1 <= i_pixel_data;
 pixel_2 <= temp_pixel;
 end
 o_clk <= 0;
 pixel_counter[$clog2(MATRIX_COLS)] = 0;
 state <= STATE_BLANK_2;
 end
 STATE_BLANK_2:
 begin
 o_oe <= 1;
 state <= STATE_BLANK_3;
 end
 STATE_BLANK_3:
 begin
 o_row_sel <= o_row_sel + 1;
 o_latch <= 1;
 first_cycle <= 1;
 if(o_row_sel == (MATRIX_ROWS/2)-2)
 begin
 pwm_counter <= pwm_counter + 1;
 end
 state <= STATE_DISPLAY_1;
 end
 endcase
 end
 end
endmodule

single_port_ram_sync.v:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
//
// Single-port RAM With Synchronous Read (Read Through)
// Modified from XST v-rams-07
//
//////////////////////////////////////////////////////////////////////////////////
module single_port_ram_sync
 #(parameter ADDR_WIDTH = 6,
 parameter DATA_WIDTH = 8,
 parameter INIT_FILE = ""
 )
 (input clk, 
 input we,
 input [ADDR_WIDTH-1:0] addr,
 input [DATA_WIDTH-1:0] din,
 output [DATA_WIDTH-1:0] dout
 );
 
 reg [DATA_WIDTH-1:0] ram [2**ADDR_WIDTH-1:0];
 reg [ADDR_WIDTH-1:0] r_addr;
 
 initial
 begin
 $readmemh(INIT_FILE, ram);
 end
 
 always @(posedge clk)
 begin
 if (we)
 begin
 ram[addr] <= din;
 end
 r_addr <= addr;
 end
 
 assign dout = ram[r_addr];
endmodule
toolic
14.6k5 gold badges29 silver badges204 bronze badges
asked Aug 12, 2022 at 22:25
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

The code follows recommended practices regarding the use of nonblocking assignments, consistent indentation and parameter usage.

One exception is the line:

 pixel_counter[$clog2(MATRIX_COLS)] = 0;

That should use a nonblocking assignment:

 pixel_counter[$clog2(MATRIX_COLS)] <= 0;

The state machine in the led_matrix_controller module has a 3-bit state register. This means that there are 8 possible values for the register (0-7). Since you only require 5 defined states (0-4), this leaves 3 unassigned states (5-7). Although you can not reach the unassigned states in a normal Verilog simulation, in silicon it may be possible if you encounter a soft error. It is a common practice to guard against this type of error by accounting for all possible states using a default clause in your case statement. For example:

 default:
 begin
 state <= STATE_DISPLAY_2;
 end
 endcase

The ~0 syntax is legal, and it does what you want, but I don't think it is all that common or easy to understand. It would be better to use the '1 syntax, which does require you to enable SystemVerilog features in your tools (most tools support SV these days). Refer to IEEE Std 1800-2017, section 5.7.1 Integer literal constants. The code would then be:

 o_row_sel <= '1;

Another common approach is to use the replicated concatenation operator to explicitly declare the signal width:

 o_row_sel <= {ROWSEL_WIDTH{1'b1}};

where you would create a parameter named ROWSEL_WIDTH, for example.

answered Aug 13, 2022 at 11:56
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.