/// LSU EE 4755 -- Fall 2024 -- Digital Design / HDL // /// Verilog Notes -- Synthesis of Comb. Behavioral Code /// Under Construction /// Contents // /// Inference of Combinational Behavioral Code /// Optimization of Inferred Combinational Behavioral Code /// References // :SV12: IEEE 1800-2012 -- The SystemVerilog Standard // :SV17: IEEE 1800-2017 -- The SystemVerilog Standard // :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. // // Genus HDL Modeling Guide -- Access from Within lsu.edu Only. // https://www.ece.lsu.edu/v/s/genus_hdlmod.pdf // ////////////////////////////////////////////////////////////////////////////// /// Inference of Combinational Behavioral Code /// Restrictions on Code // // Must be in an always or always_comb block. // // For always, sensitivity list must contain all RHS live-in variables. // // A variable is either always assigned or never assigned. // // A variable can be assigned in at most one block. /// Types of Code // // -- Assignments. Re-assignments. // // -- if / else blocks. Complete, incomplete. // // -- case blocks // // -- Loops /// Simple Cases: Variable assigned once. No control flow statements. module pie_00( output uwire x, y, input uwire a, b, c ); assign x = ~ ( a ^ b ); assign y = x & c | b; endmodule module pie_01( output logic x, y, input uwire a, b, c ); always_comb begin x = ~ ( a ^ b ); y = x & c | b; end endmodule // /// Multiple Assignments // // A var cannot be assigned in more than one block. // A var can be assigned any number of times in a block. // // Each assignment creates a new wire. // // :Example: // // The only difference between the two modules below is the name of // the connection between the XOR and NOT gates. In pie_02 that // connection is x, in pie_03 that connection is aeb. module pie_02( output var logic x, y, input uwire logic a, b, c ); always_comb begin x = a ^ b; // Line 1. x = ~ x; // Line 2. y = x & c | b; // Line 3. end endmodule // module pie_03( output logic x, y, input uwire a, b, c ); logic aeb; always_comb begin aeb = a ^ b; x = ~ aeb; y = x & c | b; end endmodule // :Example: // // Another example showing multiple assignments to the same object // in procedural code. These examples are intended to illustrate // how hardware is inferred from procedural code, not the best // way to multiply by 11. // If you had to multiply by 11, the module below is the way to do it // because we expect that the synthesis program can handle // multiplication well, and so will find an efficient way to optimize // the times-eleven case. // module mult_by_11_best_way( output logic [15:0] prod, input uwire [11:0] a ); always_comb prod = a * 11; endmodule // // The mult_by_11 modules below show how multiple assignments are // inferred. // module mult_by_11_version_00 ( output logic [15:0] prod, input uwire [11:0] a ); always_comb begin prod = a; // Line 1 prod = prod + a * 2; // Line 2 prod = prod + a * 8; // Line 3 end endmodule // ; module mult_version_00 ( output logic [15:0] prod, input uwire [11:0] a, input uwire [2:0] b ); logic [15:0] pp; always_comb begin prod = 0; pp = b[0] ? a << 0 : 0; // Line pp0 prod = prod + pp; // Line s0 pp = b[1] ? a << 1 : 0; prod = prod + pp; pp = b[2] ? a << 2 : 0; prod = prod + pp; end endmodule module mult_version_01 ( output logic [15:0] prod, input uwire [11:0] a, input uwire [2:0] b ); always_comb begin prod = b[0] ? a << 0 : 0; prod += b[1] ? a << 1 : 0; prod += b[2] ? a << 2 : 0; end endmodule ////////////////////////////////////////////////////////////////////////////// /// Inference of if Statements // // /// Case: if ( COND ) begin IFPART end else begin ELSEPART end // // - Find all variables assigned in IFPART and ELSEPART .. // .. let VARS denote these variables. // // - For each vee in VARS: // Let: vee_pre: the value of vee before the IF statement. // vee_if: the last value assigned to vee in the IF part (if any). // vee_else: the last value assigned to vee in the ELSE part (if any). // - Synthesize a multiplexor. // - Connect mux input 0 to vee_else (if it exists) or vee_pre (otherwise). // - Connect mux input 1 to vee_if (if it exists) or vee_pre (otherwise). // - The select input is connected to COND. // Consider: if ( COND ) IFPART else if ( COND2 ) IFPART2 else ELSEPART // // // :Example: // // A simple module with an if/else statement and an assignment. // module addborc ( output logic [15:0] x, input uwire [15:0] a, b, c, input uwire d ); logic [15:0] t; always_comb begin if ( d ) t = b; else t = c; // Note: VARS = {t} x = a + t; end endmodule // Inferred Hardware, with labels // // Inferred Hardware, without labels // // :Example: // // A module with both an if/else and an if. // module addborcb ( output logic [15:0] x, input uwire [15:0] a, b, c, input uwire d ); logic [15:0] t; always_comb begin if ( d ) t = b; else t = c; // Line 1 VARS = {t} if ( a < 8 ) t = t + 12; // Line 2 VARS = {t} x = a + t; // Line 3 end endmodule // // :Example: // // A module in which more than one object is modified in an if/else. // module more_stuff ( output logic [15:0] x, y, input logic [15:0] a, b, c ); always_comb begin y = 0; if ( a < b ) begin x = c * a; y = b; end else begin x = c + a; end // VARS = {x,y} end endmodule // module mult_version_02 #( logic [2:0] b = 3 ) ( output logic [15:0] prod, input uwire [11:0] a ); always_comb begin prod = b[0] ? a << 0 : 0; // L1 prod += b[1] ? a << 1 : 0; // L2 prod += b[2] ? a << 2 : 0; // L3 end endmodule module mult_version_03 ( output logic [15:0] prod, input uwire [11:0] a, input uwire [2:0] b ); always_comb begin if ( b[0] ) prod = a; else prod = 0; // L1 if ( b[1] ) prod += a << 1; // L2 if ( b[2] ) prod += a << 2; // L3 end endmodule // module mult_version_03b #( int bwidth = 3 ) ( output logic [15:0] prod, input uwire [11:0] a, input uwire [bwidth-1:0] b ); always_comb begin if ( b[0] ) prod = a << 0; else prod = 0; for ( int i=1; i<bwidth; i++ ) if ( b[i] ) prod += a << i; end endmodule module mult_version_04 #( logic [2:0] b = 1 ) ( output logic [15:0] prod, input uwire [11:0] a ); always_comb begin if ( b[0] ) prod = a << 0; else prod = 0; if ( b[1] ) prod += a << 1; if ( b[2] ) prod += a << 2; end endmodule module mult_version_05 ( output logic [15:0] prod, input uwire [11:0] a ); mult_version_03 mm(prod,a,3'd3); endmodule ////////////////////////////////////////////////////////////////////////////// /// Inference of Loops // /// Loop Statements // // :Syntax: for ( TYPE I = INIT; TEST; INCR ) BODY; // // :Example: for ( int i=0; i<3; i++ ) a[i] = b[2-i]; // /// Synthesizability Rule // // - Number of iterations must be known at elaboration time. // /// Inferred Hardware // // Let n denote the number of iterations. // // Completely unroll the loop .. // .. that is, make n copies of the loop body. // // In each copy replace I (the iteration variable) with its // value at that iteration. // :Example: // // Examples of loops which are and are not synthesizable. // module loop_synthesizability_examples #( int sz = 8 ) ( output logic [sz-1:0] x, y, z, input uwire [sz-1:0] a, b, c ); always_comb begin // Good. (Though easier ways to do a bitwise exclusive or.) // for ( int i=0; i<sz; i++ ) x[i] = a[i] + b[i]; // Bad: Number of iterations depends on c. // for ( int i=0; i < c; i++ ) y[i] = a[i]; for ( int i=c; i < sz; i++ ) y[i] = b[i]; // Also Bad: // for ( int i=0; i < sz; i++ ) begin if ( i == c ) break; // Not allowed by some synthesis programs. y[i] = a[i]; end // Good, though more code than is necessary. // for ( int i=0; i < sz; i++ ) if ( i < c ) y[i] = a[i]; for ( int i=0; i < sz; i++ ) if ( i >= c ) y[i] = b[i]; // Good: Fixed Number Of iterations. // for ( int i=0; i < sz; i++ ) y[i] = i < c ? a[i] : b[i]; for ( int i=0; i < sz; i++ ) begin if ( i < c ) begin y[i] = a[i]; end else begin y[i] = b[i]; end end end endmodule // :Example: // // Example of a behavioral combinational logic description of a loop // in which a loop iteration does not depend on a prior loop // iteration. (For an example in which iterations do depend on prior // iterations see the_hard_way, further below.) // module loop_example_good #( int sz = 8 ) ( output logic [sz-1:0] z, input uwire [sz-1:0] a, b, input uwire logic signed [sz-1:0] c ); always_comb for ( int i=0; i<sz; i++ ) z[i] = i < c ? a[i] : b[i]; endmodule // Inferred Hardware // // :Example: // // Example of a behavioral combinational logic description containing // a loop in which a value produced in one iteration, x, is used the // subsequent iteration. // module the_hard_way ( output logic x, input uwire [3:0] a, b ); always_comb begin x = 0; for ( int i=0; i<4; i++ ) x = x | ( a[i] ^ b[i] ); x = ~x; end endmodule // Inferred Hardware // : ////////////////////////////////////////////////////////////////////////////// /// Optimization of Inferred Combinational Behavioral Code // // :Def: Optimization [of an HDL Description] // The modification of an HDL description (e.g., Verilog code) .. // .. that reduces cost, .. // .. reduces critical path length (makes it faster), .. // .. or meets some other goal or constraint .. /// .. without changing what the HDL description does. // /// Where Optimization is Done // // - Synthesis Programs // Usually minimize cost while meeting timing constraints. // The result of this optimization is visible in cost and timing reports. // // - HDL Simulation Programs // Goal of optimization is to make simulation run faster. // The result of this optimization is that .. // .. users spend less time waiting for .. // .. the "Testbench completed, 0 errors found." message (or whatever). // // Our focus is on synthesis program optimization. // /// When Synthesis Optimization is Done // // Typically, optimization is performed several times. // // - After Inference // Sometimes called generic optimization. // // - After Technology Mapping // // // :Def: Hand Optimization // Optimization of an HDL description .. // .. performed by EE 4755 students .. // .. to demonstrate an understanding of what .. // .. synthesis programs are likely to do with a design. // /// Optimization Techniques // // - Constant Propagation and Folding // - Logic and arithmetic simplification. // - Substitution of equivalent modules. // :Example: // // Optimization of a two-input multiplexor with constant inputs. // // Module description (in which inputs are not constant) // module mux2(output logic x, input uwire s, a0, a1); always_comb if ( s ) x = a1; else x = a0; endmodule // Optimization of mux2 when instantiated with one or two constant // inputs (three cases shown): // // // :Example: // // Take sum of selected elements. (Fall 2015 Midterm Exam Problem 2) module ssum #( int n = 30, int f = 4, int swid = f + $clog2(n) ) ( output logic [swid-1:0] sum, input uwire [n-1:0] mask, input uwire [f-1:0] a[n] ); always_comb begin sum = 0; for ( int i=0; i<n; i++ ) if ( mask[i] ) sum += a[i]; end endmodule // Inferred Hardware (Before Optimization): // // After Some Optimization: // // :Example: // // Comparison example. Includes a loop and ifs inside ifs. module compare ( output logic gt, lt, input uwire [2:0] a, b ); always_comb begin gt = 0; lt = 0; for ( int i=2; i>=0; i-- ) if ( !gt && !lt ) begin if ( a[i] < b[i] ) lt = 1; if ( a[i] > b[i] ) gt = 1; end end endmodule // Inferred Hardware: // : // Optimization Plan: // : // After Optimization: // : // :Example: // // Inference of hardware for a module using both constant and // non-constant index operators. (E.g., v[5], v[idx_min].) (See 2016 // Midterm Exam Problem 3b.) // module min_elt #( int n = 3, w = 32, wi = $clog2(n) ) ( output logic [wi-1:0] idx_min, input uwire signed [w-1:0] v [n] ); always_comb begin idx_min = 0; for ( int i=1; i<n; i++ ) if ( v[i] < v[idx_min] ) idx_min = i; end endmodule // Inferred Hardware, plain and labeled with some Verilog: // // /// Optimization of min_elt: // // - Eliminate lower-left mux because its select signal is a constant, 0. // - Eliminate upper-left mux. (Try drawing a truth table.) // - Reduce number of inputs in the lower-right mux from 3 to 2. // - Simplify logic for upper-right mux. // // Optimized Hardware // module pop_c_good #(int width = 128, int bits = $clog2(width+1)) (output logic [bits:1] pop, input [width-1:0] vector); always_comb begin pop = 0; for ( int i=0; i<width; i++ ) pop += vector[i]; end endmodule module pop_c_bad #(int width = 128, int bits = $clog2(width+1)) (output logic [bits:1] pop, input [width-1:0] vector); initial begin pop = 0; for ( int i=0; i<width; i++ ) pop += vector[i]; end endmodule module b_syn_1 ( output logic [7:0] x, input uwire [7:0] a, b, input uwire c ); always_comb begin if ( c ) x = a + b; else x = a - b; end endmodule module b_syn_2 ( output logic [7:0] x, input uwire [7:0] a, b, input uwire c ); always_comb begin // Synthesizes to a mux with c as control. // x = a - b; if ( c ) x = a + b; // // But, bad style because x re-assigned ... makes it harder on humans. end endmodule module b_syn_bad_1 ( output logic [7:0] x, input uwire [7:0] a, b, input uwire c ); always @* begin // Not combinational logic because x not always assigned. // Shouldn't synthesize when always_comb used. // Will synthesize with "always @*", but will not be combinational. // if ( c ) x = a + b; end endmodule