/// LSU EE 4755 -- Fall 2025 -- Digital Design / HDL /// /// Rudiments of Procedural and Behavioral Code /// Contents // Behavioral Code and Structured Procedures // The initial Structured Procedure // Basics of Delay (#) and Event (@) Controls // The always Structured Procedure // The $display system task, and a testbench example. /// References // :SV: IEEE 1800-2023 -- The SystemVerilog Standard // https://ieeexplore.ieee.org/document/10458102 // This is for those already familiar with Verilog. // // :BV3: Brown & Vranesic, Fundamentals of Digital Logic with Verilog, 3rd Ed. // The text used in LSU EE 2740. //////////////////////////////////////////////////////////////////////////////// /// Procedural and Behavioral Code // In a procedural language (such as C, Python, Fortran, and other // conventional languages) statements execute one after another, // sometimes jumping forward and backward as directed by conditional // ("if"), looping ("for"), procedure call, and other constructs. (An // example of a non-procedural language is Prolog.) // // Verilog descriptions (please don't call them programs) can consist // of both procedural and non-procedural code. In non-procedural code // the order of execution does not follow a sequential path through // lines of the description, but is determined by other rules. An // example of non-procedural code would be the add4_structural module // below and the pie and two_pie modules from l005-review.v. All (or // almost all) descriptions in this file are procedural. // // Verilog procedural code is called /behavioral code/ if the intent // of the person that wrote it is to describe hardware. An example is // the add4_behavioral module below. Behavioral code is used to // specify what hardware does rather than describe it as an // interconnection of simpler components (as does structural code). // Procedural code looks like a program in a conventional programming // language but there are major differences. One difference is in // when a piece of code is supposed to start running. In C, execution // starts with the "main" routine and other procedures start // execution when they are called from code in the main routine. In // Verilog, procedural code is invoked in a variety of ways, some // times in a way similar to a procedure call, other times in // response to a change in a signal. See the material in // l007-simulation-basic.v for more on when procedural code can // execute. // Details on procedural code will be presented later. Here the // rudiments are presented here so that procedural code can be used // Verilog examples. /// :Def: Procedural Code // Statement(s) within a structured procedure, which is started with // the following keywords: // :Keywords: initial,always,always_comb,always_ff,always_latch,final // The order of execution is similar to procedural languages like C. // /// :Def: Behavioral Code // Procedural code that is part of a module which is intended to // describe hardware. This usually means that the code computes // module outputs. // // /// :Example: Procedural Behavioral Adder. // // Module add4_behavioral computes the sum of the four values at its // inputs. // module add4_behavioral #( int w = 10 ) ( output logic [w-1:0] sum, input uwire [w-1:0] a[4] ); // This code is procedural because of the always_comb block. // It is behavioral because it is intended to describe hardware. always_comb begin sum = a[0]; for ( int i=1; i<4; i++ ) sum += a[i]; end // The way to determine for sure whether the intent is to describe // hardware is to ask the person that wrote it, or to look for // comments, documentation, etc, that explain that the intent is to // model hardware. Lacking those, one can look at the code and // guess. endmodule /// :Example: Structural (Not Procedural) Adder // module add4_structural #( int w = 10 ) ( output logic [w-1:0] sum, input uwire [w-1:0] a[4] ); // This code is not procedural because it lacks procedural blocks. uwire [w-1:0] p0, p1; my_adder #(w) add01( p0, a[0], a[1] ); my_adder #(w) add23( p1, a[2], a[3] ); my_adder #(w) add0123( sum, p0, p1 ); endmodule /// :Example: Procedural But Not Behavioral Adder // module add4_not_behavioral_by_any_means #( int w = 10 ) ( input uwire [w-1:0] a[4] ); // This is procedural because of the procedural block (starting with // always_comb). // // It is not behavioral because the module does not have outputs, // it just writes to the console. // logic [w-1:0] sum; always_comb begin sum = a[0]; for ( int i=1; i<4; i++ ) sum += a[i]; $write("The sum of %0d + %0d + %0d + %0d = %0d.\n", a[0], a[1], a[2], a[3], sum); end endmodule ////////////////////////////////////////////////////////////////////////////// /// Specifying Procedural Code // // Procedural code is placed in a /structured procedure/. // When and how many times the procedural code executes depends // on the particular structured procedure. // // A structured procedure starts with one of the following keywords: // :Keyword: initial,always_comb,always_ff,always_latch,always,final // :SV: Section 9.2 // :BV3: Section A.11.1 /// Structured Procedures // // :Keyword: initial STATEMENT // Scheduling: // Fixed: Scheduled into inactive region at simulation start. // Informal Description: // Start statement at t=0. // Typical Use: // Main routine for testbench code. // // :Keyword: always_comb STATEMENT /// Intent is to describe combinational logic. // Scheduling: // Fixed: Scheduled into inactive region at simulation start. // Sensitivity List: Construct sensitivity list based on // objects used in STATEMENT. // Informal Description: // Execute statement at t=0 and whenever live-in objects change. // On each execution .. // .. every object that can be written, will be written. // Typical Use: // Model combinational logic. // // There are two additional always_FOO statements, // always_latch and always_ff. They will be covered later. // // :Keyword: always_ff @( posedge OBJECT ) STATEMENT // :Keyword: always_ff @( EDGE OBJECT ) STATEMENT /// Intent is to describe edge-triggered registers and flip-flips. // Informal Description: // Execute statement when OBJECT changes from 0 to 1 or // changes as specified by EDGE. // Typical Use: // Model edge-triggered flip-flops and registers. // // :Keyword: always_latch STATEMENT /// Intent is to describe level-sensitive registers and flip-flips. // Informal Description: // Execute statement at t=0 and whenever live-in objects change. // Objects may or may not be written // Every object that can be written, will be written. // Objects may or may not be written on a particular execution. // Typical Use: // Model level-triggered flip-flops. // Will not be used in this course. // // :Keyword: always STATEMENT // Can be used for anything. // Scheduling: // Fixed: Scheduled into inactive region at simulation start. // Scheduled into inactive region each time STATEMENT completes. // Informal Description: // Execute statement repeatedly. // STATEMENT must have a timing or event control (unless // your goal is to hang the simulator). // Typical Use: // In testbench code to generate clock signals. // In testbench code to monitor changes in other signals. // In older versions of Verilog, used to model all kinds // of hardware. // // :Keyword: final STATEMENT // Scheduling: // Scheduled into inactive region when all regions are empty. // Just once, of course. // Informal Description: // Execute statement before simulator exits. // // STATEMENT is usually a block, e.g., begin a=1; etc.... ; end //////////////////////////////////////////////////////////////////////////////// /// Initial Blocks // :SV: Section 9.2.1 // :BV3: Section A.11.1 // /// Placement // // Appear in modules and programs (programs not yet covered). // There can be any number of initial blocks in each module and program. // /// Execution Timing // // All initial blocks start at t=0. // // /// Typical Uses // // Initialization. // // Testbench. // // /// NOT USED FOR // // Describing synthesizable hardware. // :Example: // // Simple example. module mymod1(input uwire myport, output uwire alsomyport); int a, b; // Starts running at t=0. initial begin a = 1; b = myport; $write("Welcome to mymod1! Today we will set, a=%0d and b=%0h\n", a, b); end endmodule // :Example: // // More examples of initial. // Difference between simple statement and compound (begin/end) statement. // module explain_statement( input int f ); int a,aa,b,c,d,e,g; /// Initial // // Code in initial statement executes once at beginning of simulation. // There can be any number of initial statements in a module. // Simple statement. // initial a = 1; // // A verbose way of coding the line above. // initial begin aa = 1; end // Simple statement. // initial if ( f < 5 ) g = f - 1; else g = f + 3; // Compound statement, which is bracketed by begin/end. // initial begin b = 1; c = 3; end // Compound statement taking up more space. initial begin d = 4; e = 5; end `ifdef PLEASE_DONT_DEFINE_ME // ILLEGAL: Can't place procedural code here. h = 7; `endif endmodule //////////////////////////////////////////////////////////////////////////////// /// Delays and Event Controls // :SV: Section 9.4 up to and including 9.4.2.1 /// So You Don't Forget // // Delays (#) and event controls (@) used in testbenches. // // Delays are NEVER used in behavioral code. // // If behavioral code uses event controls .. // .. they must be used in a particular way (see below). /// Description // // Delay and event controls are used to pause the execution of // procedural code. With a delay, execution of the piece of procedural // code is paused for the specified amount of time. With an event // control, execution of the piece of procedural code is paused until // something happens. The pause only affects the place in the // procedural code where the delay or event control appears. During // the pause the simulator can do something else. // Here only a basic description is given. Later, more advanced // treatment will be given, including non-blocking delays and // the simulator event queue. /// Delay Control (#) // // :Keyword: # AMT STATEMENT // // Informal Description: // Wait AMT time units, then execute STATEMENT. // In many cases STATEMENT is blank. // // Scheduling: // Suspend process and schedule it into inactive region .. // .. with timestamp t + AMT. /// Event Control (@, wait) // // :Keyword: @ ( A or B or ... ) STATEMENT // :Keyword: @ ( posedge A ) STATEMENT // Scheduling: // Suspend process and set up sensitivity list based on // objects used in the expression. Processes will be scheduled // into inactive region when an object in the sensitivity // list changes. // Informal Description: // Wait for A or B to change, then execute statement. // Sensitivity List: the "A or B or ..." part. // // :Keyword: @* STATEMENT // // Informal Description: // Wait until any source (live-in) object in STATEMENT changes. // The "*" is called an implicit event expression list. // // Scheduling: // Suspend process and set up sensitivity list based on // objects used in statement. Processes will be scheduled // into inactive region when an object in the sensitivity // list changes. /// Level-Sensitive Event Control (Wait Statement ) // // :Keyword: wait ( COND ) STATEMENT // Scheduling: // If COND is false suspend process and set up // sensitivity list based on objects used in the // expression. Processes will be scheduled into // inactive region when an object in the sensitivity // list changes. On resumption re-execute wait. // Informal Description: // Wait (if necesary) for COND to be true, then execute STATEMENT. // :Example: // // Simple delay and event control examples. Chosen to show what they // do, but not what they are typically used for. // module delay_and_event_demo(input uwire [15:0] b, c, x); logic [15:0] a; initial begin a = 1; // Assignment occurs at t = 0; // Below, a changes at t = 5. // #5 a = 2; // AMT -> 5, STATEMENT -> "a = 2;" // // Now t = 5 = 0 + 5; #7; // AMT -> 7, STATEMENT -> "" (Statement is empty.) // // Now t = 12 = 5 + 7 // a is assigned at t = 12. a = 3; // Suppose at t = 12, b = 120 // @ ( b ); // Wait until b changes. Keep waiting. Be patient. // // Finally, b changes! It's a new day! // Now t = 86412. We thank you for your patience. @ ( a or b ); // Wait until either a or b or both change. // // For this module, is there any hope that a will change while // waiting in the event control above? @ ( posedge b ); // Wait until b changes from 0 to 1. @ ( negedge b ); // Wait until b changes from 1 to 0. // Assume that b changed from 1 to 0 at t=100020. a = 4; // a assigned at t = 100020. // Wait until b changes. (b is used as a source in STATEMENT.) // @* a = b + c; // STATEMENT -> "a = b + c;" // If condition is not true wait until it is. // wait ( x == a + b ); // // At this point x == a + b. // If it was true just before reaching the wait .. // .. then the wait time was zero. // Wait for condition to change. There will always be a wait here. // @ ( x or c ); end endmodule module my_counter #( int w = 4 ) ( output logic [w-1:0] cnt, input uwire reset, clk ); always_ff @( posedge clk ) cnt = reset ? 0 : cnt + 1; endmodule // :Example: // // Using an initial block and timing controls to generate a clock. // // See the demo_the_better_way for another example of how to use // initial and delay controls for something useful. // module demo_blocks ( output logic [15:0] the_count, input uwire reset, not_sure_what_to_use_this_for ); logic [15:0] a; logic clk, clock; my_counter #(16) mc1( the_count, reset, clk ); // Need to generate a signal, clk, that alternates between 0 and 1: // // t = 0; clk -> 0 // t = 5: clk -> 1 // t = 10: clk -> 0 // t = 15: clk -> 1 // t = 20: clk -> 0 // initial begin clk = 0; a = 1; while ( 1 ) begin #5; clk = !clk; end end // This will continue until something stops the simulator. // Generate a clock using a more compact coding style: initial clock = 0; initial while ( 1 ) #5 clock = !clock; // // Note: can replace "while ( 1 )" with "forever". // Because of the infinite loops above, this final will never // execute. // final $write("This is module demo_blocks saying goodbye.\n"); endmodule //////////////////////////////////////////////////////////////////////////////// /// Always Blocks // :SV: Section 9.2.2 // :BV3: Section A.11.1 /// Description // // :Keyword: always STATEMENT // // Note: SHOULD NOT be used for behavioral descriptions. // // - Executes STATEMENT at t=0 .. // .. when statement finishes it executes it again (at the same time step).. // .. forever and ever and ever. // Note: The amount of time STATEMENT takes depends on its contents. // Assume that STATEMENT always takes 5 units. // - Then STATEMENT will finish at t=5 ... // - ... and then will start again at t=5 (hence always) ... // - ... and will finish at t=10 ... // - ... and start again at t=10 ... // // Note: There will be an infinite loop when STATEMENT // does not contain some kind of delay or event control. // // :Keyword: always_comb STATEMENT // // Restriction: STATEMENT cannot have timing (e.g, #3;) // nor event controls (@(a or b);). // // - STATEMENT executes at t=0. // - STATEMENT executes whenever an object used in STATEMENT changes. /// Common Use of Always -- Describe Combinational Logic // // :Sample: always @* STATEMENT // :Sample: always_comb STATEMENT module always_example ( output logic [15:0] sum, input uwire [15:0] a, b, input uwire clk); always_comb begin sum = a + b; if ( sum < a + b + 0 ) sum = 16'hffff; end endmodule // :Example: // // Use of always to generate a clock signal. module myclockgen(output logic clock); initial clock = 0; always #5 clock = !clock; endmodule // :Example: // // Good use of always, once upon a time. /// Now it's bad! // module mymod2foolish(output logic x,y, input uwire a,b,c); // Runs each time a or b or c changes. // always @( a or b or c ) begin /// FOOLISH! Use always_comb instead. x = a + 1; y = b + c; end endmodule // :Example: // // Unusual use of always. Probably an error. // module mymod2bad(output logic x,y,z, input uwire a,b,c); // Runs each time a or b changes. Won't run if only c changes. // always @( a or b ) begin /// Note: c intentionally omitted. x = a + 1; y = b + c; end endmodule // :Example: // // Use of implicit event expression. always_comb // module mymod21(x,y,z,a,b,c); input uwire a, b, c; output logic x, y, z; // Runs at t=0 and each time any right-hand-side object changes. // always_comb begin x = a + 1; y = b + c; end endmodule /// Multiple initial And always Blocks // A module can have any number of initial and always constructs. // Timing and other details will be discussed later. module mymod3(output logic x,y,z, input uwire a,b,c); // Runs at initialization. initial begin x = 1; end // Runs at initialization .. // .. either before or after the block above. initial begin y = 1; end // Runs each time x changes. // always @( * ) begin y = 2 + x; end endmodule // :Example: // // Uses of always to generate clocks and other signals. // module demo_blocks_2 ( output logic [15:0] b, e, e1, q, q1, q2, input uwire [15:0] f, d ); logic [15:0] c, c1, a; logic clock, clk; int cycle; // // Generate a clock signal using always // localparam int cycle_limit = 1000; initial begin clock = 0; cycle = 0; wait ( cycle == cycle_limit ); $write("Hello. %0d cycles have elapsed, the limit. Bye.\n", cycle ); $finish(2); end always begin #5; clock = !clock; cycle++; end initial clk = 0; always #5 clk = !clk; // This is an infinite loop that will freeze the simulator time at zero. `ifdef FREEZE_WITH_AN_INFINITE_LOOP always i++; `endif always @( posedge clk ) $write("Tic\n"); always @( negedge clk ) begin $write("Toc\n"); end always_ff @( posedge clk ) b = f + 1; // Executes at t=0 and whenever b or d changes. // always_comb q = b + d; // // Equivalent in old-school Verilog // always @* q1 = b + d; // // Equivalent in very old-school Verilog. // always @( b or d ) q2 = b + d; // WARNING: Points deducted. // // The style above is risky because it's easy to accidentally omit // a variable from the event list. always_comb begin c = b + d; if ( f == 4755 ) e = 0; if ( c < f ) e++; else e--; end always_latch begin c1 = b + d; if ( f == 4755 ) e1 = c1; end final $write("This is module demo_blocks_2 saying goodbye.\n"); endmodule /////////////////////////////////////////////////////////////////////////////// /// Procedural Assignments v. Continuous Assignments /// /// :Def: Procedural Assignment // An assignment statement in procedural code. // module an_adderc(output logic [9:0] s, input uwire [9:0] a,b); logic [9:0] s2; always_comb begin s2 = a + b; s = s2 + 5; end endmodule /// :Def: Continuous Assignment // An assignment at module (not procedural) scope // that is re-executed whenever objects // on the right-hand change. // module an_adder(output uwire [9:0] s, input uwire [9:0] a,b); uwire [9:0] s2 = a + b; assign s = s2 + 5; endmodule // // :Keyword: assign // The assign keyword SHOULD NOT be used in procedural code. // In this class it SHALL NOT be used in procedural code. // // :Syntax: assign NET_OBJ = EXPR; // :Sample: assign s = b + c; // // EXPR is executed whenever an object in EXPR changes. /// Procedural Assignment versus Continuous Assignment // // Procedural: // Statements execute in order. // Statement must be in a structured procedure. // Continuous: // Statements execute whenever objects in expression change. // Statements must be at module scope (in this class). module add3_behavioral_a #( int w = 10 ) ( output logic [w-1:0] sum, input uwire [w-1:0] a[3] ); always_comb begin sum = a[0] + a[1] + a[2]; end endmodule module add3_explicit_str_a #( int w = 10 ) ( output uwire [w-1:0] sum, input uwire [w-1:0] a[3] ); assign sum = a[0] + a[1] + a[2]; endmodule module add3_behavioral_b #( int w = 10 ) ( output logic [w-1:0] sum, input uwire [w-1:0] a[3] ); logic [w-1:0] sum12; always_comb begin sum12 = a[0] + a[1]; sum = sum12 + a[2]; end endmodule module add3_explicit_str_b #( int w = 10 ) ( output uwire [w-1:0] sum, input uwire [w-1:0] a[3] ); uwire [w-1:0] sum12; assign sum = sum12 + a[2]; assign sum12 = a[0] + a[1]; endmodule `ifdef DONT_DEFINE_ME `endif //////////////////////////////////////////////////////////////////////////////// /// Simple Testbench, and the $write System Task // :SV: Section 21.2 -- Display and Write system tasks. // The following are useful in procedures. They are covered briefly // below and will be covered in detail later. // :Keyword: $write // :Keyword: $display // // The $display and $write system tasks. // Used in procedures to print messages in the transcript. // Similar to the C printf library function. // // :Syntax: $write(FORMAT,ARG1,ARG2,...) // FORMAT is a string that can contain escape sequences. // ARG1, ARG2 are the values to be printed. // Format escape sequences start with a % and followed by a format character. // Format characters: %d (decimal), %h (hex), %o (octal), %b (binary) // %c (character), %s(string), %t(time), ... // // Examples: // $write("The values are: i=%0d or %h (hex) time=%t\n", i, i, $time); // This is one of many system tasks. (See :SV: Section 20) // :Example: // // Use of behavioral code to write a testbench for a simple "imply" // module. The testbench is in module demo_the_tedious_way. module imply( output uwire x, input uwire a, b ); assign x = ~a | b; endmodule module demo_the_tedious_way(); logic a, b; uwire r; imply imp1(r, a, b); initial begin $write("\n --- Output of demo_the_tedious_way ---\n"); // Here t = 0 (units discussed later). a = 0; b = 0; $write("At the very beginning, t = %0t, r = %d\n", $time, r ); // This delays execution for one unit. #1; $write("A little later, t = %0d, r = %d\n",$time,r); #1; a = 0; b = 1; $write(" t = %0t, r = %d\n",$time,r); #1; $write(" t = %0t, r = %d\n",$time,r); #1; a = 1; b = 0; #1; $write(" t = %0t, r = %d\n",$time,r); #1; a = 1; b = 1; #1; $write(" t = %0t, r = %d\n",$time,r); end endmodule // :Example: // // Same testbench as code above, but using a for loop to simplify // things. // module demo_the_better_way(); int i; uwire a = i[0]; uwire b = i[1]; uwire r; imply imp1(r, a, b); initial begin #100; // Wait for the other testbench to finish. $write("\n --- Output of demo_the_better_way ---\n"); for ( i=0; i<4; i++ ) begin $write("At the beginning of an iteration. t=%0t, i=%2b r=%d.\n", $time, i, r ); #1; $write("In the middle of an iteration. t=%0t, i=%2b r=%d.\n", $time, i, r ); #1; end end endmodule // :Example: // // Same testbench as code above, but checks whether output is // correct and reports a tally of errors at the end. // module demo_testbench(); logic [1:0] i; uwire a = i[0]; uwire b = i[1]; uwire r; imply imp1(r, a, b); initial begin automatic int n_err = 0; #200; // Wait for the other testbenchs to finish. $write("\n --- Output of demo_testbench ---\n"); for ( i=0; i<4; i++ ) begin logic shadow_r; #1; // Compute expected output. shadow_r = ~a | b; if ( shadow_r !== r ) begin n_err++; $write("Incorrect result for a=%h, b=%h: %h != %h (correct).\n", a, b, r, shadow_r); end #1; end $write("Done with tests, number of errors, %0d\n",n_err); $stop; end endmodule module my_adder #(int w=20)(output uwire [w-1:0] s, input uwire [w-1:0] a,b); assign s = a + b; endmodule