Coding for synthesis best practices in Verilog - Time & Space Complexity
When writing Verilog code for synthesis, it is important to understand how the code's execution time grows as input size changes.
We want to see how the main operations in the code repeat and affect performance.
Analyze the time complexity of the following Verilog code snippet.
module sum_array #(parameter N = 8) (
input wire [7:0] data [0:N-1],
output reg [15:0] sum
);
integer i;
always @* begin
sum = 0;
for (i = 0; i < N; i = i + 1) begin
sum = sum + data[i];
end
end
endmodule
This code sums all elements of an input array of size N and outputs the total.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The for-loop that adds each array element to the sum.
- How many times: The loop runs exactly N times, once per array element.
As the array size N grows, the number of additions grows linearly.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | 10 additions |
| 100 | 100 additions |
| 1000 | 1000 additions |
Pattern observation: Doubling the input size doubles the number of operations.
Time Complexity: O(N)
This means the time to complete the sum grows directly in proportion to the number of elements.
[X] Wrong: "The loop runs in constant time because hardware runs in parallel."
[OK] Correct: While hardware can do many things at once, this loop describes a sequential addition that depends on previous results, so it takes time proportional to N.
Understanding how loops and operations scale in hardware description languages shows your grasp of efficient hardware design and synthesis constraints.
"What if we replaced the for-loop with a tree-based adder structure? How would the time complexity change?"
Practice
Solution
Step 1: Understand combinational logic coding
Combinational logic should be written using blocking assignments (=) to ensure correct immediate updates within thealways @(*)block.Step 2: Identify synthesis best practices
Using non-blocking assignments or delays in combinational blocks can cause unintended behavior or non-synthesizable code.Final Answer:
Use blocking assignments (=) inside an always @(*) block. -> Option DQuick Check:
Combinational logic = blocking assignments [OK]
- Using non-blocking assignments in combinational blocks
- Adding delays (#) which are not synthesizable
- Leaving outputs unassigned causing inferred latches
Solution
Step 1: Identify combinational block sensitivity
Combinational logic should react to all input changes, soalways @(*)automatically includes all signals read inside the block.Step 2: Compare other options
Options with clock edges or specific signals are used for sequential logic, not combinational.Final Answer:
always @(*) -> Option AQuick Check:
Combinational sensitivity = always @(*) [OK]
- Using clock edges in combinational blocks
- Listing incomplete sensitivity signals manually
- Forgetting to include all inputs causing latches
clk rises?reg q; always @(posedge clk) begin q = d; end
Solution
Step 1: Analyze assignment type in sequential block
The code uses blocking assignment (=) inside a clockedalwaysblock, which is not recommended for registers.Step 2: Understand synthesis vs simulation behavior
While synthesis tools may infer the register correctly, simulation can show unexpected behavior due to immediate assignment instead of scheduled update.Final Answer:
q updates but may cause simulation vs synthesis mismatch -> Option AQuick Check:
Sequential block + blocking assignment = mismatch risk [OK]
- Using blocking (=) in clocked always blocks
- Expecting immediate update in sequential logic
- Confusing simulation behavior with synthesis
always @(posedge clk) begin
if (enable)
q <= d;
else
; // missing else assignment
endSolution
Step 1: Check assignments in all branches
The else branch has no assignment toq, soqretains its previous value, causing a latch to be inferred.Step 2: Understand synthesis best practice
To avoid latches, all outputs must be assigned in every branch of a sequential block.Final Answer:
Missing non-blocking assignment in else branch causes inferred latch -> Option CQuick Check:
All branches assign outputs to avoid latches [OK]
- Leaving else branch empty causing inferred latch
- Confusing blocking and non-blocking assignments
- Ignoring sensitivity list completeness in sequential blocks
Solution
Step 1: Identify synchronous reset and non-blocking assignments
Synchronous reset means reset is checked only on clock edge, so sensitivity list should only haveposedge clk. Non-blocking assignments (<=) must be used for registers.Step 2: Evaluate each option
always @(posedge clk or posedge reset) begin if (reset) count <= 0; else count = count + 1; end uses asynchronous reset (posedge reset) and blocking assignment in else branch, which is not best practice. always @(posedge clk) begin if (reset == 1'b1) count = 0; else count <= count + 1; end mixes blocking and non-blocking assignments. always @(posedge clk) begin if (reset) count <= 0; else count = count + 1; end uses blocking assignment in else branch. Only always @(posedge clk) begin if (reset) count <= 0; else count <= count + 1; end uses synchronous reset and non-blocking assignments consistently.Final Answer:
Option B correctly implements a 4-bit counter with synchronous reset and proper assignments. -> Option BQuick Check:
Synchronous reset + non-blocking assignments = always @(posedge clk) begin if (reset) count <= 0; else count <= count + 1; end [OK]
- Mixing blocking and non-blocking assignments in sequential logic
- Using asynchronous reset when synchronous is required
- Including reset in sensitivity list for synchronous reset
Ready to prove it?
Pass the test and this pattern moves unknown
