[ Chapter start ] [ Previous page ] [ Next page ] 12.5 Verilog and Logic SynthesisA top-down design approach using Verilog begins with a single module at the top of the hierarchy to model the input and output response of the ASIC: module MyChip_ASIC(); ... (code to model ASIC I/O) ... endmodule ; This top-level Verilog module is used to simulate the ASIC I/O connections and any bus I/O during the earliest stages of design. Often the reason that designs fail is lack of attention to the connection between the ASIC and the rest of the system. As a designer, you proceed down through the hierarchy as you add lower-level modules to the top-level Verilog module. Initially the lower-level modules are just empty placeholders, or stubs , containing a minimum of code. For example, you might start by using inverters just to connect inputs directly to the outputs. You expand these stubs before moving down to the next level of modules. // behavioral "always", etc. ... module SecondLevelStub1() ... assign Output1 = ~Input1; endmodule module SecondLevelStub2() ... assign Output2 = ~Input2; Eventually the Verilog modules will correspond to the various component pieces of the ASIC. 12.5.1 Verilog ModelingBefore we could start synthesis of the Viterbi decoder we had to alter the model for the D flip-flop. This was because the original flip-flop model contained syntax (multiple wait statements in an always statement) that was acceptable to the simulation tool but not by the synthesis tool. This example was artificial because we had already prepared and tested the Verilog code so that it was acceptable to the synthesis software (we say we created synthesizable code). However, finding ourselves with nonsynthesizable code arises frequently in logic synthesis. The original OVI LRM included a synthesis policy , a set of guidelines that outline which parts of the Verilog language a synthesis tool should support and which parts are optional. Some EDA vendors call their synthesis policy a modeling style . There is no current standard on which parts of an HDL (either Verilog or VHDL) a synthesis tool should support. It is essential that the structural model created by a synthesis tool is functionally identical , or functionally equivalent , to your behavioral model. Hopefully, we know this is true if the synthesis tool is working properly. In this case the logic is “correct by construction.” If you use different HDL code for simulation and for synthesis, you have a problem. The process of formal verification can prove that two logic descriptions (perhaps structural and behavioral HDL descriptions) are identical in their behavior. We shall return to this issue in Chapter 13. Next we shall examine Verilog and VHDL from the following viewpoint: “How do I write synthesizable code?” 12.5.2 Delays in VerilogSynthesis tools ignore delay values. They must—how can a synthesis tool guarantee that logic will have a certain delay? For example, a synthesizer cannot generate hardware to implement the following Verilog code: input clk; output [2:0] phase; reg [2:0] phase; phase <= #1 4'b0001; phase <= #2 4'b0010; phase <= #3 4'b0011; phase <= #4 4'b0100; We can avoid this type of timing problem by dividing a clock as follows: module Step_Count (clk_5x, phase); input clk_5x; output [2:0] phase; reg [2:0] phase; 0:phase = #1 1; 1:phase = #1 2; 2:phase = #1 3; 3:phase = #1 4; 12.5.3 Blocking and Nonblocking AssignmentsThere are some synthesis limitations that arise from the different types of Verilog assignment statements. Consider the following shift-register model: module race(clk, q0); input clk, q0; reg q1, q2; always @( posedge clk) q1 = #1 q0; always @( posedge clk) q2 = #1 q1; This example has a race condition (or a race ) that occurs as follows. The synthesizer ignores delays and the two always statements are procedures that execute concurrently. So, do we update q1 first and then assign the new value of q1 to q2 ? or do we update q2 first (with the old value of q1 ), and then update q1 ? In real hardware two signals would be racing each other—and the winner is unclear. We must think like the hardware to guide the synthesis tool. Combining the assignment statements into a single always statement, as follows, is one way to solve this problem: module no_race_1(clk, q0, q2); input clk, q0; output q2; reg q1, q2; always @( posedge clk) begin q2 = q1; q1 = q0; end Evaluation is sequential within an always statement, and the order of the assignment statements now ensures q2 gets the old value of q1 —before we update q1 . We can also avoid the problem if we use nonblocking assignment statements, module no_race_2(clk, q0, q2); input clk, q0; output q2; reg q1, q2; always @( posedge clk) q1 <= #1 q0; always @( posedge clk) q2 <= #1 q1; This code updates all the registers together, at the end of a time step, so q2 always gets the old value of q1 . 12.5.4 Combinational Logic in VerilogTo model combinational logic, the sensitivity list of a Verilog always statement must contain only signals with no edges (no reference to keywords posedge or negedge ). This is a level-sensitive sensitivity list—as in the following example that implies a two-input AND gate: module And_Always(x, y, z); input x,y; output z; reg z; always @(x or y) z <= x & y; // combinational logic method 1 Continuous assignment statements also imply combinational logic (notice that z is now a wire rather than a reg ), module And_Assign(x, y, z); input x,y; output z; wire z; assign z <= x & y; // combinational logic method 2 = method 1 We may also use concatenation or bit reduction to synthesize combinational logic functions, module And_Or (a,b,c,z); input a,b,c; output z; reg [1:0]z; always @(a or b or c) begin z[1]<= &{a,b,c}; z[2]<= |{a,b,c}; end module Parity (BusIn, outp); input [7:0] BusIn; output outp; reg outp; always @(BusIn) if (^Busin == 0) outp = 1; else outp = 0; The number of inputs, the types, and the drive strengths of the synthesized combinational logic cells will depend on the speed, area, and load requirements that you set as constraints. You must be careful if you reference a signal ( reg or wire ) in a level-sensitive always statement and do not include that signal in the sensitivity list. In the following example, signal b is missing from the sensitivity list, and so this code should be flagged with a warning or an error by the synthesis tool—even though the code is perfectly legal and acceptable to the Verilog simulator: module And_Bad(a, b, c); input a, b; output c; reg c; always @(a) c <= a & b; // b is missing from this sensitivity list It is easy to write Verilog code that will simulate, but that does not make sense to the synthesis software. You must think like the hardware. To avoid this type of problem with combinational logic inside an always statement you should either: For example, consider the following two models: module CL_good(a, b, c); input a, b; output c; reg c; begin c = a + b; d = a & b; e = c + d; end // c, d: LHS before RHS module CL_bad(a, b, c); input a, b; output c; reg c; begin e = c + d; c = a + b; d = a & b; end // c, d: RHS before LHS In CL_bad , the signals c and d are used on the right-hand side (RHS) of an assignment statement before they are defined on the left-hand side (LHS) of an assignment statement. If the logic synthesizer produces combinational logic for CL_bad , it should warn us that the synthesized logic may not match the simulation results. When you are describing combinational logic you should be aware of the complexity of logic optimization. Some combinational logic functions are too difficult for the optimization algorithms to handle. The following module, Achilles , and large parity functions are examples of hard-to-synthesize functions. This is because most logic-optimization algorithms calculate the complement of the functions at some point. The complements of certain functions grow exponentially in the number of their product terms. // The complement of this function is too big for synthesis. module Achilles (out, in); output out; input [30:1] in; assign out = in[30]&in[29]&in[28] | in[27]&in[26]&in[25] | in[24]&in[23]&in[22] | in[21]&in[20]&in[19] | in[18]&in[17]&in[16] | in[15]&in[14]&in[13] | in[12]&in[11]&in[10] | in[9] & in[8]&in[7] | in[6] & in[5]&in[4] | in[3] & in[2]&in[1]; In a case like this you can isolate the problem function in a separate module. Then, after synthesis, you can use directives to tell the synthesizer not to try and optimize the problem function. 12.5.5 Multiplexers In VerilogWe imply a MUX using a case statement, as in the following example: module Mux_21a(sel, a, b, z); input sel, a , b; output z; reg z; begin case (sel) 1'b0: z <= a; 1'b1: z <= b; end Be careful using 'x' in a case statement. Metalogical values (such as 'x' ) are not “real” and are only valid in simulation (and they are sometimes known as simbits for that reason). For example, a synthesizer cannot make logic to model the following and will usually issue a warning to that effect: module Mux_x(sel, a, b, z); input sel, a, b; output z; reg z; begin case (sel) 1'b0: z <= 0; 1'b1: z <= 1; 1'bx: z <= 'x'; end For the same reason you should avoid using casex and casez statements. An if statement can also be used to imply a MUX as follows: module Mux_21b(sel, a, b, z); input sel, a, b; output z; reg z; always @(a or b or sel) begin if (sel) z <= a else z <= b; end However, if you do not always assign to an output, as in the following code, you will get a latch: module Mux_Latch(sel, a, b, z); input sel, a, b; output z; reg z; always @(a or sel) begin if (sel) z <= a; end It is important to understand why this code implies a sequential latch and not a combinational MUX. Think like the hardware and you will see the problem. When sel is zero, you can pass through the always statement whenever a change occurs on the input a without updating the value of the output z . In this situation you need to “remember” the value of z when a changes. This implies sequential logic using a as the latch input, sel as the active-high latch enable, and z as the latch output. The following code implies an 8:1 MUX with a three-state output: module Mux_81(InBus, sel, OE, OutBit); input [7:0] InBus; input [2:0] Sel; input OE; output OutBit; reg OutBit; if (OE == 1) OutBit = InBus[sel]; else OutBit = 1'bz; When you synthesize a large MUX the required speed and area, the output load, as well as the cells that are available in the cell library will determine whether the synthesizer uses a large MUX cell, several smaller MUX cells, or equivalent random logic cells. The synthesized logic may also use different logic cells depending on whether you want the fastest path from the select input to the MUX output or from the data inputs to the MUX output. 12.5.6 The Verilog Case Statementmodule case8_oneHot(oneHot, a, b, c, z); input a, b, c; input [2:0] oneHot; output z; reg z; always @(oneHot or a or b or c) begin case (oneHot) //synopsys full_case 3'b001: z <= a; 3'b010: z <= b; 3'b100: z <= c; By including the default choice, the case statement is exhaustive . This means that every possible value of the select variable ( oneHot ) is accounted for in the arms of the case statement. In some synthesizers (Synopsys, for example) you may indicate the arms are exhaustive and imply a MUX by using a compiler directive or synthesis directive . A compiler directive is also called a pseudocomment if it uses the comment format (such as //synopsys full_case ). The format of pseudocomments is very specific. Thus, for example, //synopys may be recognized but // synopys (with an extra space) or //SynopSys (uppercase) may not. The use of pseudocomments shows the problems of using an HDL for a purpose for which it was not intended. When we start “extending” the language we lose the advantages of a standard and sacrifice portability. A compiler directive in module case8_oneHot is unnecessary if the default choice is included. If you omit the default choice and you do not have the ability to use the full_case directive (or you use a different tool), the synthesizer will infer latches for the output z . If the default in a case statement is 'x' (signifying a synthesis don’t care value ), this gives the synthesizer flexibility in optimizing the logic. It does not mean that the synthesized logic output will be unknown when the default applies. The combinational logic that results from a case statement when a don’t care ( 'x' ) is included as a default may or may not include a MUX, depending on how the logic is optimized. In case8_oneHot the choices in the arms of the case statement are exhaustive and also mutually exclusive . Consider the following alternative model: module case8_priority(oneHot, a, b, c, z); input a, b, c; input [2:0] oneHot; output z; reg z; always @(oneHot or a or b or c) begin case (1'b1) //synopsys parallel_case In this version of the case statement the choices are not necessarily mutually exclusive ( oneHot[0] and oneHot[2] may both be equal to 1'b1 , for example). Thus the code implies a priority encoder. This may not be what you intended. Some logic synthesizers allow you to indicate mutually exclusive choices by using a directive ( //synopsys parallel_case , for example). It is probably wiser not to use these “outside-the-language” directives if they can be avoided. 12.5.7 Decoders In VerilogThe following code models a 4:16 decoder with enable and three-state output: module Decoder_4To16(enable, In_4, Out_16); // 4-to-16 decoder input enable; input [3:0] In_4; output [15:0] Out_16; begin Out_16 = 16'h0000; Out_16[In_4] = 1; end In line 7 the binary-encoded 4-bit input sets the corresponding bit of the 16-bit output to '1' . The synthesizer infers a three-state buffer from the assignment in line 5 . Using the equality operator, '==' , rather than the case equality operator, '===' , makes sense in line 6 , because the synthesizer cannot generate logic that will check for enable being 'x' or 'z' . So, for example, do not write the following (though some synthesis tools will still accept it): if (enable === 1) // can't make logic to check for enable = x or z 12.5.8 Priority Encoder in VerilogThe following Verilog code models a priority encoder with three-state output: module Pri_Encoder32 (InBus, Clk, OE, OutBus); input [31:0]InBus; input OE, Clk; output [4:0]OutBus; for (j = 31; j >= 0; j = j - 1) begin if (InBus[j] == 1) OutBus = j; end In lines 9 – 11 the binary-encoded output is set to the position of the lowest-indexed '1' in the input bus. The logic synthesizer must be able to unroll the loop in a for statement. Normally the synthesizer will check for fixed (or static) bounds on the loop limits, as in line 9 above. 12.5.9 Arithmetic in VerilogYou need to make room for the carry bit when you add two numbers in Verilog. You may do this using concatenation on the LHS of an assignment as follows: module Adder_8 (A, B, Z, Cin, Cout); input [7:0] A, B; input Cin; output [7:0] Z; output Cout; assign {Cout, Z} = A + B + Cin; In the following example, the synthesizer should recognize '1' as a carry-in bit of an adder and should synthesize one adder and not two: module Adder_16 (A, B, Sum, Cout); input [15:0] A, B; output [15:0] Sum; output Cout; always @(A or B) {Cout, Sum} = A + B + 1; It is always possible to synthesize adders (and other arithmetic functions) using random logic, but they may not be as efficient as using datapath synthesis (see Section 12.5.12 ). A logic sythesizer may infer two adders from the following description rather than shaping a single adder. module Add_A (sel, a, b, c, d, y); input a, b, c, d, sel; output y; reg y; always @(sel or a or b or c or d) begin if (sel == 0) y <= a + b; else y <= c + d; end To imply the presence of a MUX before a single adder we can use temporary variables. For example, the synthesizer should use only one adder for the following code: module Add_B (sel, a, b, c, d, y); input a, b, c, d, sel; output y; reg t1, t2, y; always @(sel or a or b or c or d) begin if (sel == 0) begin t1 = a; t2 = b; end // Temporary else begin t1 = c; t2 = d; end // variables. If a synthesis tool is capable of performing resource allocation and resource sharing in these situations, the coding style may not matter. However we may want to use a different tool, which may not be as advanced, at a later date—so it is better to use Add_B rather than Add_A if we wish to conserve area. This example shows that the simplest code ( Add_A ) does not always result in the simplest logic ( Add_B ). Multiplication in Verilog assumes nets are unsigned numbers: module Multiply_unsigned (A, B, Z); input [1:0] A, B; output [3:0] Z; To multiply signed numbers we need to extend the multiplicands with their sign bits as follows (some simulators have trouble with the concatenation '{}' structures, in which case we have to write them out “long hand”): module Multiply_signed (A, B, Z); input [1:0] A, B; output [3:0] Z; // 00 -> 00_00 01 -> 00_01 10 -> 11_10 11 -> 11_11 assign Z = { { 2{A[1]} }, A} * { { 2{B[1]} }, B}; How the logic synthesizer implements the multiplication depends on the software. 12.5.10 Sequential Logic in VerilogThe following statement implies a positive-edge–triggered D flip-flop: always @( posedge clock) Q_flipflop = D; // A flip-flop. When you use edges ( posedge or negedge ) in the sensitivity list of an always statement, you imply a clocked storage element. However, an always statement does not have to be edge-sensitive to imply sequential logic. As another example of sequential logic, the following statement implies a level-sensitive transparent latch: always @(clock or D) if (clock) Q_latch = D; // A latch. On the negative edge of the clock the always statement is executed, but no assignment is made to Q_latch . These last two code examples concisely illustrate the difference between a flip-flop and a latch. Any sequential logic cell or memory element must be initialized. Although you could use an initial statement to simulate power-up, generating logic to mimic an initial statement is hard. Instead use a reset as follows: always @( posedge clock or negedge reset) A problem now arises. When we use two edges, the synthesizer must infer which edge is the clock, and which is the reset. Synthesis tools cannot read any significance into the names we have chosen. For example, we could have written always @( posedge day or negedge year) —but which is the clock and which is the reset in this case? For most synthesis tools you must solve this problem by writing HDL code in a certain format or pattern so that the logic synthesizer may correctly infer the clock and reset signals. The following examples show one possible pattern or template . These templates and their use are usually described in a synthesis style guide that is part of the synthesis software documentation. always @( posedge clk or negedge reset) begin // template for reset: if (reset == 0) Q = 0; // initialize, else Q = D; // normal clocking module Counter_With_Reset (count, clock, reset); input clock, reset; output count; reg [7:0] count; always @ ( posedge clock or negedge reset) if (reset == 0) count = 0; else count = count + 1; module DFF_MasterSlave (D, clock, reset, Q); // D type flip-flop input D, clock, reset; output Q; reg Q, latch; always @( posedge clock or posedge reset) if (reset == 1) latch = 0; else latch = D; // the master. always @(latch) Q = latch; // the slave. The synthesis tool can now infer that, in these templates, the signal that is tested in the if statement is the reset, and that the other signal must therefore be the clock. 12.5.11 Component Instantiation in VerilogWhen we give an HDL description to a synthesis tool, it will synthesize a netlist that contains generic logic gates. By generic we mean the logic is technology-independent (it could be CMOS standard cell, FPGA, TTL, GaAs, or something else—we have not decided yet). Only after logic optimization and mapping to a specific ASIC cell library do the speed or area constraints determine the cell choices from a cell library: NAND gates, OAI gates, and so on. The only way to ensure that the synthesizer uses a particular cell, 'special' for example, from a specific library is to write structural Verilog and instantiate the cell, 'special' , in the Verilog. We call this hand instantiation . We must then decide whether to allow logic optimization to replace or change 'special' . If we insist on using logic cell 'special' and do not want it changed, we flag the cell with a synthesizer command. Most logic synthesizers currently use a pseudocomment statement or set an attribute to do this. For example, we might include the following statement to tell the Compass synthesizer—“Do not change cell instance my_inv_8x .” This is not a standard construct, and it is not portable from tool to tool either. //Compass dontTouch my_inv_8x or // synopsys dont_touch INVD8 my_inv_8x(.I(a), .ZN(b) ); ( some compiler directives are trademarks). Notice, in this example, instantiation involves declaring the instance name and defining a structural port mapping. There is no standard name for technology-independent models or components—we shall call them soft models or standard components . We can use the standard components for synthesis or for behavioral Verilog simulation. Here is an example of using standard components for flip-flops (remember there are no primitive Verilog flip-flop models—only primitives for the elementary logic cells): module Count4(clk, reset, Q0, Q1, Q2, Q3); input clk, reset; output Q0, Q1, Q2, Q3; wire Q0, Q1, Q2, Q3; asDff dff0( Q0, ~Q0, clk, reset); // The asDff is a asDff dff1( Q1, ~Q1, Q0, reset); // standard component, asDff dff2( Q2, ~Q2, Q1, reset); // unique to one set of tools. asDff dff3( Q3, ~Q3, Q2, reset); The asDff and other standard components are provided with the synthesis tool. The standard components have specific names and interfaces that are part of the software documentation. When we use a standard component such as asDff we are saying: “I want a D flip-flop, but I do not know which ASIC technology I want to use—give me a generic version. I do not want to write a Verilog model for the D flip-flop myself because I do not want to bother to synthesize each and every instance of a flip-flop. When the time comes, just map this generic flip-flop to whatever is available in the technology-dependent (vendor-specific) library.” If we try and simulate Count4 we will get an error, :Count4.v: L5: error: Module 'asDff' not defined (and three more like this) because asDff is not a primitive Verilog model. The synthesis tool should provide us with a model for the standard component. For example, the following code models the behavior of the standard component, asDff : module asDff (D, Q, Clk, Rst); parameter width = 1, reset_value = 0; input [width-1:0] D; output [width-1:0] Q; reg [width-1:0] Q; input Clk,Rst; initial Q = {width{1'bx}}; always @ ( posedge Clk or negedge Rst ) if ( Rst==0 ) Q <= #1 reset_value; else Q <= #1 D; When the synthesizer compiles the HDL code in Count4 , it does not parse the asDff model. The software recognizes asDff and says “I see you want a flip-flop.” The first steps that the synthesis software and the simulation software take are often referred to as compilation, but the two steps are different for each of these tools. Synopsys has an extensive set of libraries, called DesignWare , that contains standard components not only for flip-flops but for arithmetic and other complex logic elements. These standard components are kept protected from optimization until it is time to map to a vendor technology. ASIC or EDA companies that produce design software and cell libraries can tune the synthesizer to the silicon and achieve a more efficient mapping. Even though we call them standard components, there are no standards that cover their names, use, interfaces, or models. 12.5.12 Datapath Synthesis in VerilogDatapath synthesis is used for bus-wide arithmetic and other bus-wide operations. For example, synthesis of a 32-bit multiplier in random logic is much less efficient than using datapath synthesis. There are several approaches to datapath synthesis:
In all cases the disadvantage is that the code becomes specific to a certain piece of software. Here are two examples of datapath synthesis directives: module DP_csum(A1,B1,Z1); input [3:0] A1,B1; output Z1; reg [3:0] Z1; always @(A1 or B1) Z1 <= A1 + B1;//Compass adder_arch cond_sum_add module DP_ripp(A2,B2,Z2); input [3:0] A2,B2; output Z2; reg [3:0] Z2; always @(A2 or B2) Z2 <= A2 + B2;//Compass adder_arch ripple_add These directives steer the synthesis of a conditional-sum adder (usually the fastest adder implementation) or a ripple-carry adder (small but slow). There are some limitations to datapath synthesis. Sometimes, complex operations are not synthesized as we might expect. For example, a datapath library may contain a subtracter that has a carry input; however, the following code may synthesize to random logic, because the synthesizer may not be able to infer that the signal CarryIn is a subtracter carry: module DP_sub_A(A,B,OutBus,CarryIn); input [3:0] A, B ; input CarryIn ; output OutBus ; reg [3:0] OutBus ; always @(A or B or CarryIn) OutBus <= A - B - CarryIn ; If we rewrite the code and subtract the carry as a constant, the synthesizer can more easily infer that it should use the carry-in of a datapath subtracter: module DP_sub_B (A, B, CarryIn, Z) ; input [3:0] A, B, CarryIn ; output [3:0] Z; reg [3:0] Z; always @(A or B or CarryIn) begin default : Z <= A - B - 1'b0; endcase This is another example of thinking like the hardware in order to help the synthesis tool infer what we are trying to imply. [ Chapter start ] [ Previous page ] [ Next page ] |
© 2024 Internet Business Systems, Inc. 670 Aberdeen Way, Milpitas, CA 95035 +1 (408) 882-6554 — Contact Us, or visit our other sites: |
|
Privacy PolicyAdvertise |