Welcome FPGA engineers to join the official WeChat technical group.
Clickthe blue textto follow us at FPGA Home – the best and largest pure FPGA engineer community in China.
Copyright Statement:This article is an original article by CSDN blogger ‘FPGADesigner’ Original link:https://bestfpga.blog.csdn.net/article/details/102784167
Using logic gates and continuous assignments to model circuits is a relatively detailed way to describe hardware. Using procedural blocks can describe a system from a higher level, known as behavioral modeling.
1. Procedural Assignment
The differences between blocking and non-blocking assignments are well known. Here are two features documented.
1.1 Feature 1
In most cases, non-blocking assignments are the last executed assignment statements at a point in time. See the example code below:
module test
(
input clk,
output reg a, b
);
always @ (posedge clk) begin
a = 0;
b = 1;
a <= b;
b <= a;
end
endmodule
Non-blocking assignments can be thought of as including two steps: (1) evaluate and schedule, first obtaining the value on the right side of the non-blocking assignment and scheduling this assignment to occur at the end of the current time point. (2) At the end of the current time point, update the left side value. Therefore, the result of this code is a = 1, b = 0.
1.2 Feature 2
If there are multiple non-blocking assignments for the same variable within a procedural block, these non-blocking assignments will execute in order (but I think it cannot simply be said that the procedural block is ‘executed in order’, as this can be misleading; it should be said that it has a certain ‘sequential characteristic’).
See the example code below:
module test
(
input clk,
output reg a, b
);
always @ (posedge clk) begin
a <= 0;
a <= 1;
end
endmodule
There are two assignment statements for variable a within the always block, but due to the sequential characteristic, the assignment result of a should be 1. Utilizing this feature, the following code writing style is often seen:
always @ (posedge clk) begin
a <= 0;
if (flag == 1)
a <= 1;
end
Only when flag=1, a will be 1.
2. Procedural Continuous Assignment
This assignment method allows continuous driving of networks or variables within a procedural block. However, this modeling method cannot be synthesized, so only the effects of two types of continuous assignments are briefly recorded here.
assign and deassign: assign continuous assignment will take priority over a variable, making other procedural blocks that assign to this variable ineffective. deassign continuous assignment will release the occupation relationship.
See the example code below:
`timescale 1ns / 1ps
module sim();
reg clk = 0, rst_n = 0, d = 1;
reg q;
//test i1
//(
// .clk (clk),
// .rst_n(rst),
// .q(q),
// .d(d)
//);
always @ (rst_n)
if (!rst_n) assign q = 0;
else deassign q;
always @ (posedge clk)
q <= d;
always #5 clk <= ~clk;
initial begin
#50 rst_n = 1;
end
endmodule
When rst_n=0, the assign continuous assignment occupies q, making the value of q always 0; when rst_n=1, deassign releases the occupation, and the value of q is determined by other procedural assignments, changing with d at the rising edge of clk.
force and release: the functions are the same as assign and deassign, except that the assignment object can be either a variable or a network. When the object of the force assignment is a network, it will invalidate all other drivers to that network.
3. case statement
The default branch of the case statement is not mandatory, as long as the designer is clear about the design intent. Here are two relatively uncommon but sometimes particularly useful usages of case.
3.1 do-not-cares
Includes two types: • casez indicates that the high-impedance state (z) is not cared about; • casex indicates that both the high-impedance state (z) and unknown state (x) are not cared about.
Using ‘?’ on the bits that are not cared about makes it more convenient. casex and casez can be fully synthesized, for example, the code below can implement a priority encoder:
module test
(
input clk,
input [3:0] d,
output reg [15:0] q
);
always @ (posedge clk)
casez (d)
4'b1??? : q <= 1;
4'b01?? : q <= 2;
4'b001? : q <= 3;
4'b0001 : q <= 4;
endcase
endmodule
If you want to use casex and casez, you still need to consider whether it can be synthesized from the ‘design’ perspective, and ensure proper simulation after synthesis.
For example, the above code can achieve the same effect using a case statement + 16 branches, which can be synthesized. The use of casez serves more to simplify code design.
3.2 constant case
In the case statement, constant expressions can be used, and this constant will be compared with the expressions in each branch. For example, the code below:
module test
(
input clk,
input [3:0] d,
output reg [15:0] q
);
always @ (posedge clk)
casez (1)
d[0] : q <= 1;
d[1] : q <= 2;
d[2] : q <= 3;
d[3] : q <= 4;
endcase
endmodule
Can achieve, based on which bit in d is 1, execute the corresponding code. If multiple bits in d are 1 simultaneously, then multiple branches will be satisfied at the same time, and the first one in order will be executed. In any case, it is still necessary to be clear about the design intent and ensure proper simulation work.
4. Loop Statements
forever and repeat are completely non-synthesizable and are only used in simulation files.
Loop statements for and while are not completely non-synthesizable, but because Verilog is modeling hardware, the use of for and while is certainly not as flexible as in software programming languages. Again, the old saying, it should be considered from the perspective of whether it can be synthesized from the ‘design’ perspective.
If there are issues with the use of loop statements, synthesis tools will give prompts, such as the Vivado prompt message below:
[Synth 8-3380] loop condition does not converge after 2000 iterations
Using loops well can simplify code design. An example of a register chain is shown below (the same effect can be achieved using for):
module test
(
input clk, rst_n,
input [15:0] d,
output reg [15:0] q
);
integer i;
(* keep = "true" *)reg [15:0] mem [7:0];
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
i = 0;
while(i < 8) begin
mem[i] <= 0;
i = i + 1'b1;
end
end
else begin
i = 0;
mem[0] <= d;
while(i < 7) begin
mem[i+1] <= mem[i];
i = i + 1'b1;
end
end
q <= mem[7];
end
endmodule
The corresponding RTL schematic is as follows:
Considering from a software programming mindset, loop statements are executed one by one in a process. However, from the design results above, it is clear that all statements in the while loop are executed ‘simultaneously’, and the code merely rewrites many assignment statements with repetitive characteristics in the form of while/for.
5. Procedural Blocks
Procedural blocks (procedure) include four types: initial structure, always structure, task (task), function (function). Here are two not very familiar features documented.
5.1 Zero-delay infinite loop
Always blocks in simulation files must be used with some timing control. If there is no timing control in the always block, the simulation will get stuck at a time point. For example, the following statement is often used to create a clock signal:
always #10 clk = ~clk;
If it is written in the following form:
always clk = ~clk;
It forms a zero-delay infinite loop, and the simulation time will get stuck at 0s and cannot advance. Running this code can cause the program to hang or even crash the system, requiring a restart.
5.2 initial for initialization
The initial block can be synthesized but cannot include timing control statements, hence its function is limited and is generally used for variable initialization. For example, the code below:
reg [15:0] mem [7:0];
integer i;
initial begin
for (i = 0; i < 8; i=i+1)
mem[i] = i;
end
6. Procedural Block Timing Control
This feature is mainly used in simulation files, although some also involve hardware design. Verilog has two explicit timing control types: delay control and event expressions.
6.1 Delay Control
Used to control the execution time of statements, such as describing the waveform of the stimulus. The delay value can be an expression, for example, “#d rega = regb;”, this assignment statement will execute after a delay of d time units. (1) If the result of d is high impedance (z) or unknown (x), it is treated as 0; (2) If the result of d is a negative number, it will be treated as an unsigned number, as shown in the code below:
parameter [7:0] delay = -50;
initial begin
rst_n = 0;
#(-delay) rst_n = 1;
#delay rst_n = 0;
end
rst_n will become 1 after a delay of 50 clock cycles; since the 8-bit -50 in binary two’s complement is treated as an unsigned number, the value will be 206, thus rst_n will change to 0 after a delay of 206 clock cycles.
6.2 Event Expressions
Statements will only execute when certain simulation events occur. The change in value of networks or variables is called implicit events; designers can set named events, which may be triggered by other procedural blocks, called explicit events.
Changes in values or the direction of changes (rising edge posedge or falling edge negedge) are implicit events. Although they are often used with always (e.g., always @ (posedge clk)), they can be used more flexibly in simulation files. See the example code below:
// example1: statement executes at rising edge of clk
reg [7:0] delay = 0;
initial begin
forever @(posedge clk) delay = delay + 1'b1;
end
// example2: statement executes when clk changes
reg [7:0] delay = 0;
always begin
@(clk) delay = delay + 1'b1;
end
If posedge and negedge detect an expression or multi-bit data, only the LSB’s edge change will be detected. As shown:
// example3
reg [2:0] cnt = 0;
always @ (posedge clk)
cnt <= cnt + 1'b1;
reg [7:0] delay = 0;
always begin
@(posedge cnt) delay = delay + 1'b1;
end
Detecting the rising edge of a 3-bit variable cnt is equivalent to detecting the edge event of cnt[0].
Events (event) are another data type in Verilog besides variables and networks. If an identifier is declared as an event type, it is called a ‘named event’ and must be explicitly triggered. Although events are a data type, they do not have any ‘data’. See the example:
event trig; // declare named event
reg [2:0] cnt = 0;
always @ (posedge clk) begin
cnt <= cnt + 1'b1;
if (cnt == 7) -> trig; // explicitly trigger event
end
reg [7:0] delay = 0;
always begin
@(trig) delay = delay + 1'b1; // capture event
end
Using named events can effectively implement communication and synchronization between multiple procedural blocks.
If the execution of statements in procedural blocks is sensitive to multiple events, the logical or property of events can be used. In the event sensitivity list, use ‘or’ or ‘,’ (these two symbols are equivalent), e.g., “always @ (posedge clka or posedge clkb, trig)”.
Another feature is the implicit event expression symbol ‘@*’, which adds all variables and networks read in the procedural timing control statements to the event expression.
6.3 wait statement
The above event control methods are edge-sensitive. The wait statement can also control the timing of procedural blocks, executing statements only when a condition becomes true, which is known as level-sensitive.
If the condition in wait is false, the procedural block will block until the condition becomes true, at which point the subsequent statements will execute. For example, the code below:
reg [7:0] cnta = 0, cntb = 0;
initial begin
wait(en) #10 cnta <= 60;
#10 cntb <= 70;
end
For begin…end (sequential block), wait will prevent the execution of the sequential block until en is 1, at which point the two assignment statements for cnta and cntb will execute. If using fork…join (parallel block), then the wait statement in the above code will only be effective for the assignment of cnta; it is best to also add a block declaration (begin…end or fork…join) for the wait statement.
6.4 Intra-assignment timing control
Timing control between assignments and event control is another timing control method, such as:
a = #5 b;
Which is different from “#5 a = b;”, the expression on the right side of the assignment will be evaluated immediately; the delay and event only control when this value is assigned to the left side of the assignment. For example, the above code is equivalent to:
begin
temp = b;
#5 a = temp;
end
Using the timing control between assignments feature can cleverly complete some behavioral modeling. For example, the code below can avoid ‘competition’ between assignment statements, achieving data exchange:
fork // parallel block, there is competition
#5 a = b;
#5 b = a;
join
fork // data exchange
a = #5 b;
b = #5 a;
join
Timing control between assignments will first evaluate the value of the right side expression, and after the delay, assign this value to the left side, thus the above code is equivalent to swapping the values of a and b. Many tools implement this feature of Verilog timing control between assignments using temporary storage to hold the value of the right-hand expression.
You can also use event control:
a = @(posedge clk) b;
// equivalent to
begin
temp = b;
@(posedge clk) a = temp;
end
Timing control between assignments has another feature that allows using repeat to control the number of times the delay or event executes, such as:
a = repeat(3) @(posedge clk) b;
// equivalent to
begin
temp = b;
@(posedge clk);
@(posedge clk);
@(posedge clk); a = temp;
end
It should be noted that if a variable form is used “repeat (num)”:
• If num is an unsigned number: when num is negative, it is treated as the unsigned number corresponding to the two’s complement. For example, num = -1, repeat(num) is equivalent to repeat(7).
• If num is a signed number: when num is 0 or negative, this statement will never be executed.
7. Block
A block (block) is a combination of assignment statements, including:
• Sequential block begin-end: statements in the block are executed in the given order, so the delay and event control in the block act as isolators. The start time of the sequential block is the moment the first statement begins executing, and the end time is when the last statement finishes executing.
• Parallel block fork-join: statements in the block execute simultaneously, meaning all statements start at the same time. The end time of the parallel block is when all statements have finished executing.
7.1 Nested Blocks
Multiple nested blocks are usually used to implement more complex control logic, so it is best to understand the start and end times of each block. Here are a few examples:
// Example1
begin
fork
@Aevent;
@Bevent;
join
areg = breg;
end
Due to the parallel nature of fork-join, events A and B can occur in any order, and after the fork-join block ends, the assignment statement areg = breg will execute.
// Example2
begin
begin
@Aevent;
@Bevent;
end
areg = breg;
end
If replaced with begin-end, the triggering of events must follow the given order. If event B occurs first, followed by event A, then the inner nested begin-end block will have to wait for the occurrence of event B.
// Example3
fork
@Aevent;
begin #ta wa = 0; #ta wa = 1; end
@Bevent;
begin #tb wb = 1; #tb wb = 0; end
join
The execution of the two sequential blocks in fork-join is controlled by two events. Due to the parallel nature of fork-join, the triggering and execution of the two begin-end blocks are also parallel.
7.2 Named Blocks
Each block can be named after begin and fork, called a named block. Other statements can reference the named block via this name, and the most common usage is ‘named block + disable’.
The disable statement can terminate the execution of the named block, generally used to handle exceptions, such as the code below:
begin : block_name
...
if (a == 0)
disable block_name;
...
end
When a == 0 is satisfied, the begin-end block will terminate execution. The disable will terminate the execution of the entire named block, including all other blocks and called tasks within the named block. This feature can achieve two functions: • Terminate a loop statement (equivalent to break in C language) • Skip certain states in a loop (equivalent to continue in C language)
Although Verilog does not directly provide keywords similar to break and continue in C language, the feature can be implemented using ‘named block + disable’. See the example code below:
reg [7:0] cnt = 0;
always @ (posedge clk) cnt <= cnt + 1'b1;
reg [7:0] data;
integer i;
initial begin : break
for (i = 0; i < 100; i = i + 1) begin : continue
@(posedge clk)
if (cnt == 5)
disable break;
data <= cnt;
end
end
In the for loop, when a certain condition is met, ” disable break; ” will terminate the execution of the begin-end block after initial, thus terminating the entire loop.
If changed to ” disable continue; “, when the condition is met, it will terminate the execution of the begin-end block after the for loop, thus only terminating the current loop state without affecting the next iteration of the loop.

Welcome communication engineers and FPGA engineers to follow the official account.

National Largest FPGA WeChat Technical Group
Welcome everyone to join the national FPGA WeChat technical group, which has tens of thousands of engineers, a group of engineers who love technology, where FPGA engineers help each other, share, and have a strong technical atmosphere! Hurry up and call your friends to join!!
Press and hold to join the FPGA national technical group.
FPGA Home Component City
Advantage component services, please contact the group owner: Jin Juan Email: [email protected] Welcome to recommend to purchasing
ACTEL, AD part advantage ordering (operating the full series):
XILINX, ALTERA advantage stock or ordering (operating the full series):
(The above devices are part of the models, for more models please consult the group owner Jin Juan)
Service concept: FPGA Home Component City aims to provide engineers with fast and convenient component purchasing services. After years of dedicated service, our customer service covers large listed companies, military scientific research units, and small and medium-sized enterprises. Our biggest advantage is emphasizing the service-first concept, and achieving fast delivery and favorable prices!
Directly operated brands: Xilinx ALTERA ADI TI NXP ST E2V, Micron and over a hundred other component brands, especially good at dealing with components subjected to US export restrictions to China. We welcome engineer friends to recommend us to procurement or consult us personally! We will continue to provide the best service in the industry!
Official thanks to brands of FPGA technology group: Xilinx, Intel (Altera), Microsemi (Actel), Lattice, Vantis, Quicklogic, Lucent, etc.