Fundamentals of FPGA Design: Verilog Behavioral Modeling

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.

Fundamentals of FPGA Design: Verilog Behavioral Modeling

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:

Fundamentals of FPGA Design: Verilog Behavioral Modeling

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.

Fundamentals of FPGA Design: Verilog Behavioral Modeling

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

Fundamentals of FPGA Design: Verilog Behavioral Modeling

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!!

Fundamentals of FPGA Design: Verilog Behavioral Modeling

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):

Fundamentals of FPGA Design: Verilog Behavioral Modeling

XILINX, ALTERA advantage stock or ordering (operating the full series):

Fundamentals of FPGA Design: Verilog Behavioral Modeling

(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!

Fundamentals of FPGA Design: Verilog Behavioral Modeling

Official thanks to brands of FPGA technology group: Xilinx, Intel (Altera), Microsemi (Actel), Lattice, Vantis, Quicklogic, Lucent, etc.

Leave a Comment

×