3
\$\begingroup\$

I built this simple blinker in SystemVerilog and would very much like some help to make it better:

control.sv

module control
(
 input clk,
 input button_0,
 input button_1,
 output [2:0] led
);
 localparam WAIT_TIME = 13500000;
 reg [2:0] ledCounter = 0;
 reg [23:0] clockCounter = 0;
 reg start = 0;
 always @(posedge clk) begin
 if (button_0 == 0) start <= 1;
 if (button_1 == 0) start <= 0;
 if (start == 1) begin
 clockCounter <= clockCounter + 1;
 if (clockCounter == WAIT_TIME) begin
 clockCounter <= 0;
 ledCounter <= ledCounter + 1;
 end
 end
 end
 assign led = ~ledCounter;
endmodule

Testbench: control_tb.sv:

module control_tb;
 // CONFIG
 parameter DURATION = 2000;
 parameter DELAY_5 = 5;
 parameter DELAY_10 = 10;
 // GENERATE SIM
 initial begin
 $dumpfile("control_tb.vcd"); 
 $dumpvars(0, control_tb); 
 #(DURATION); 
 $finish; 
 end
 // TEST
 reg clk, button_0, button_1;
 wire [2:0] led;
 always #(DELAY_5) clk = ~clk;
 initial begin
 clk <= 0;
 button_0 <= 1;
 button_1 <= 1;
 #(DELAY_10);
 repeat (5) #(DELAY_10);
 button_0 <= 0;
 repeat (5) #(DELAY_10);
 button_0 <= 1;
 repeat (100) #(DELAY_10);
 button_1 <= 0;
 repeat (5) #(DELAY_10);
 button_1 <= 1;
 end
 control control_uut(
 .clk(clk),
 .button_0(button_0),
 .button_1(button_1),
 .led(led)
 );
endmodule

Please feel free to comment on anything and everything that in will make this code better.

toolic
14.6k5 gold badges29 silver badges204 bronze badges
asked Jul 24 at 22:07
\$\endgroup\$
1
  • \$\begingroup\$ If the answer was helpful, please Accept it. \$\endgroup\$ Commented Sep 6 at 22:23

1 Answer 1

3
\$\begingroup\$

The code follows good practices in a number of areas, such as:

  • Good partitioning of the code into a testbench module and design modules.
  • Usage of ANSI-style ports
  • Module instance connections-by-name. Although it is more verbose than connections-by-position, it avoids a common Verilog pitfall.

Here are some general style suggestions.

Documentation

It would be good to add block comments at the top of each module to summarize the purpose:

/*
 Control for simple LED blinker.
 List of features ...
 Describe the module ports ...
*/
module control

Naming

The name control is too generic. Consider blinker_control instead. This is important as you start connecting modules together in larger designs.

Numbers

Use underscore characters to make large numeric literals easier to read and understand. For example:

localparam WAIT_TIME = 13500000;

is better as:

localparam WAIT_TIME = 13_500_000;

It would be helpful to add a comment to specify the time units (such as "ns").

Sequential logic

It is not a good practice to have multiple nonblocking assignments to the same signal like this:

if (button_0 == 0) start <= 1;
if (button_1 == 0) start <= 0;

It is more common to use an if/else:

if (button_1 == 0) begin
 start <= 0;
end else if (button_0 == 0) begin
 start <= 1;
end

It would be better to separate this logic into its own always block:

always @(posedge clk) begin
 if (button_1 == 0) begin
 start <= 0;
 end else if (button_0 == 0) begin
 start <= 1;
 end
end

It is tempting to cram all logic into a single always block, but as the design becomes larger, it is better to partition the code into many blocks.

For lines like this:

if (start == 1) begin

it is common and simpler to omit the comparison to 1:

if (start) begin

There are also multiple nonblocking assignments to clockCounter. This is preferable:

always @(posedge clk) begin
 if (start) begin
 if (clockCounter == WAIT_TIME) begin
 clockCounter <= 0;
 ledCounter <= ledCounter + 1;
 end else begin
 clockCounter <= clockCounter + 1;
 end
 end
end

For sequential logic, consider using always_ff, which is a SystemVerilog feature:

always_ff @(posedge clk) begin

The advantage is that it better conveys the design intent and it provides some built-in checks. However, this would force you to add a reset input signal to the module so that you are not initializing registers during declaration. You would initialize registers inside the always_ff blocks instead.


Testbench

When I view signal waveforms, I see that the design input signals change at the negedge of the clock signal. However, that is not evident from the testbench code because you use # delays.

I recommend changing the testbench to drive the signals on the posedge of the clock as you do inside the design:

initial begin
 clk <= 0;
 button_0 <= 1;
 button_1 <= 1;
 #(DELAY_10);
 repeat (5) @(posedge clk);
 button_0 <= 0;
 repeat (5) @(posedge clk);
 button_0 <= 1;
 repeat (100) @(posedge clk);
 button_1 <= 0;
 repeat (5) @(posedge clk);
 button_1 <= 1;
end

Currently you declare the design inputs as 4-state reg types in the testbench:

reg clk, button_0, button_1;

It would be better to use the 2-state bit type instead:

bit clk, button_0, button_1;

There is rarely the need to drive inputs as x or z, and 2-state signals offer simulation performance advantages.

Constants

It is great that you use named parameters for the constants.

These 2 seem related to one another:

parameter DELAY_5 = 5;
parameter DELAY_10 = 10;

Consider replacing them with:

parameter CLK_PERIOD = 10;

This name conveys more meaning. You can generate the clock with:

always #(CLK_PERIOD/2) clk = ~clk;
answered Jul 25 at 0:47
\$\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.