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 theDISPLAY_1 andDISPLAY_2 states to read pixel data and generate an address for the BRAM each clock cycle. In theDISPLAY_2 state, it writes the pixel data to registers connected to theo_color1 ando_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 ofo_clk. After driving the data for all the pixels in a row, it increments theo_row_sel register to select the next two rows.
led_matrix_controller.v:
`timescale 1ns / 1psmodule 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 endendmodulesingle_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];endmodule1 Answer1
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 theled_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 asoft error. It is a common practice to guard against this type of error by accounting for all possible states using adefault clause in yourcase statement. For example:
default: begin state <= STATE_DISPLAY_2; end endcaseThe~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.1Integer 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 namedROWSEL_WIDTH, for example.
You mustlog in to answer this question.
Explore related questions
See similar questions with these tags.
