/// EE 4755 - Digital Design Using HDLs // // Classroom demo code. //////////////////////////////////////////////////////////////////////////////// /// Binary Multiplication Algorithm /// Long Hand Procedure Review // // Multiply 5 times 12 in binary: // // 0101 cand -- Multiplicand // 1100 plier -- Multiplier // """" // 0000 Partial Product // 0000 // 0101 // 0101 // """"""" // 0111100 prod -- Product //////////////////////////////////////////////////////////////////////////////// /// Delay, Critical Path, and Latency /// :Def: Critical Path // The longest path starting at a launch point and ending at // a capture point. // // In a combinational circuit the launch points are usually the // module inputs and the capture points are usually the module // outputs. // // In a sequential circuit typically: // // - All register outputs are launch points. // - Module inputs may or may not be launch points. // - All register inputs are capture points. // - Module outputs may or may not be capture points. // /// :Def: Latency [of an action in a sequential circuit] // Product of number of cycles needed to complete the action .. // .. and the clock period. `default_nettype none ////////////////////////////////////////////////////////////////////////////// /// Behavioral Multiplier module mult_behav_1 #(int w = 16) (output uwire [2*w-1:0] prod, input uwire [w-1:0] plier, cand); assign prod = plier * cand; endmodule ////////////////////////////////////////////////////////////////////////////// /// Linear Multiplier /// Simple Adder, Don't Modify module carry_prop_adder #(int w=16) (output uwire [w:1] s, input uwire [w:1] a,b); assign s = a + b; endmodule module mult_linear #(int w = 16) (output logic [2*w-1:0] prod, input uwire [w-1:0] plier, cand); logic [2*w-1:0] rsum [w-1:-1]; assign rsum[-1] = 0; for ( genvar i=0; i accum=0 -> : lg lg w + 1 // plier << pos : 2 lg w // Critical Path // plier << pos -> accum += -> accum = : 2 lg w + 4w + 2 // Register Delay: 6 // // Latency // w ( 4w + 2lg w + 2 + 6 ) = 4w^2 + 2 w lg w + 8w ////////////////////////////////////////////////////////////////////////////// /// Sequential Multiplier, Using Instantiated Adder // // Simple multiplier, no handshaking. // // The cost of mult_seq_ga is about the same as mult_seq. module mult_seq_ga #( int w = 16 ) ( output logic [2*w-1:0] prod, input uwire [w-1:0] plier, cand, input uwire clk ); localparam int wlog = $clog2(w); bit [wlog-1:0] pos; bit [2*w-1:0] accum; uwire [2*w-1:0] sum; uwire [2*w-1:0] pp = cand[pos] ? plier << pos : 0; carry_prop_adder #(2*w) ga( sum, accum, pp ); always_ff @( posedge clk ) pos <= pos + 1; always_ff @( posedge clk ) if ( pos == 0 ) begin prod = sum; accum = 0; end else begin accum = sum; end endmodule ////////////////////////////////////////////////////////////////////////////// /// Streamlined Sequential Multiplier /// Techniques For Lowering Cost // // Instead of shifting the multiplier, shift the accumulator. // Use part of the accumulator to store the multiplicand. module mult_seq_stream #( int w = 16 ) ( output logic [2*w-1:0] prod, input uwire [w-1:0] plier, cand, input uwire clk); localparam int wlog = $clog2(w); bit [wlog-1:0] pos; logic [2*w-1:0] accum; always_ff @( posedge clk ) begin logic [w:0] pp; if ( pos == 0 ) begin prod = accum; accum = cand; pos = w - 1; end else begin pos--; end // Note: the multiplicand is in the lower bits of the accumulator. // pp = accum[0] ? { 1'b0, plier } : 0; // Add on the partial product and shift the accumulator. // accum = { { 1'b0, accum[2*w-1:w] } + pp, accum[w-1:1] }; end endmodule // Image:40em:ill-mul-seq-str.plain.svg /// Cost Analysis : Timing Analysis // // Regs: prod, accum: 2 2 7 w = 28 w // Regs: pos: 7 lg w // pos == 0 : lg w : lg lg w // if ( ) prod = accum: 2 3 w : 2 // if ( ) accum = cand; : w + 3w = 4w : 2 // if ( ) pos = w-1 / pos-- : 3 lg w : 2 // pos--: 9 lg w : 4 + 2 lg w // // pp = accum[0] ? { 1'b0, plier } : 0;: w : 1 // accum = { 1'b0, accum[2*w-1:w] } + pp: 9w : 2 + 2w // // Total cost: 28w + 7lg w + lg w + 6w + 4w + 3 lg w + 9 lg w + w + 9 w // = 48 w + 20 lg w // // Critical Path // pos == 0 -> accum=cand -> accum[0] ? : 0 -> + pp : // lg lg w + 2 + 1 + 2 + 2w = 2w + lg lg w + 5 ≅ 2w + 5 // Register Delay: 6 // // Latency // w ( 6 + 2w + 5 ) = 2w^2 + 11 /// Synthesis Data // `ifdef XXX Module Name Area Period Period Total Init. Target Actual Latency Interv mult_behav_1_w8 53168 1000 6062 6062 6062 mult_behav_1_w16 215672 1000 13551 13551 13551 mult_behav_1_w32 764479 1000 26485 26485 26485 mult_behav_1_w64 2891332 1000 52332 52332 52332 mult_seq_w8 56081 1000 8120 64960 64960 mult_seq_w16 122475 1000 15916 254656 254656 mult_seq_w32 258750 1000 31385 1004320 1004320 mult_seq_w64 544285 1000 58476 3742464 3742464 mult_seq_stream_w8 44320 1000 4518 36144 36144 mult_seq_stream_w16 78395 1000 8868 141888 141888 mult_seq_stream_w32 153863 1000 16361 523552 523552 mult_seq_stream_w64 304047 1000 30276 1937664 1937664 endmodule `endif // // As expected, cost grows linearly with w, as does the clock period. // Cost of streamlined much lower then mult_seq, as is the clock period. // But, latency is still O(w^2) (it quadruples when w is doubled). ////////////////////////////////////////////////////////////////////////////// /// Degree-m Sequential Multipliers // Compute m partial products in each iteration. // // Will the synthesis program figure it out? module mult_seq_m #( int w = 16, int m = 2 ) ( output logic [2*w-1:0] prod, input uwire [w-1:0] plier, cand, input uwire clk); localparam int iterations = ( w + m - 1 ) / m; localparam int iter_lg = $clog2(iterations); bit [iter_lg:1] iter; logic [2*w-1:0] accum; always_ff @( posedge clk ) begin if ( iter == iter_lg'(iterations) ) begin prod = accum; accum = 0; iter = 0; end for ( int i=0; i accum=0;iter=0; -> cand_2d[iter] -> * -> << -> + // (2 + lg[w/m]) + 1 + 2 lg[ w/m] + ( 6m + 2w ) + 2 lg[w/m] + 4w // = 6 w + 6 m + 5 lg[w/m] + 3 // // Latency // w/m ( 6 w + 6 m + 5 lg[w/m] + 3 + 6 ) // = 6 w^2/m + 9 w/m + 6w + 5w/m lg[w/m] ////////////////////////////////////////////////////////////////////////////// /// Workfront Sequential Multipliers // /// Goal: Avoid O(w) Clock Period Imposed by Ripple Adder // // Idea: // Consider the grid of binary full adders (BFAs) that might be // synthesized for module mult_linear. // // Let B[i][j] denote the BFA handling .. // .. bit j of .. // .. the partial product for multiplicand bit i. // // Call the input ports: B[i][j].a, B[i][j].b, B[i][j].ci // .. and the output ports: B[i][j].sum, B[i][j].co // // We know that the carry output B[i][j].co .. // .. connects to B[i][j+1].ci. // // We also know that output B[i][j].sum .. // .. connects to B[i+1][j].a. // // So, if B[i][j], B[i+1][j-1], and B[i-1][j+1] .. // .. are all computed at, say, cycle x, .. // .. then B[i+1][j] and B[i][j+1] can be computed in cycle x+1. // // Input B[i][j].b = cand[i] && plier[i+j] // does not depend on any BFA .. // .. and so does not restrict at which cycle B[i][j] can execute. // // A possible plan is, in cycle c, to execute .. // { B[i][j] : i + j = c, i in [0,w-1], j in [0,2w-1] } // For example, // Cycle 0: B[0][0] // Cycle 1: B[0][1], B[1][0] // Cycle 2: B[0][2], B[1][1], B[2][0] // Cycle 3: B[0][3], B[1][2], B[2][1], B[3][0] // // Note that B[0][0], B[0][1], etc. would all be the same BFA. // // The arrangement above has the disadvantage of using just one // BFA in cycle 0, one in cycle 1, etc. The set of active BFAs is // called the workfront. The workfront modules described here // connect the BFAs differently, keeping the size of the workfront at // w throughout the multiplication. That's achieved by setting: // // B[i][j].a = B[i-1][j-1].sum // B[i][j].ci = B[i][j-1].sum // B[i][j].b = plier[j] && cand[w-1-i] for j <= w; // // Here are the active BFAs with this scheme: // Cycle 0: B[0][0], B[1][0], B[2][0], ... B[w-1][0] // Cycle 1: B[0][1], B[1][1], B[2][1], ... B[w-1][1] // // Also, note that with this scheme .. // .. prod[cyc] = B[w-1][cyc].sum // // Module mult_seq_wfront implements this scheme. Note that only w // BFAs are needed, and that multiplication takes 2w cycles. // module mult_seq_wfront #( int w = 16 ) ( output logic [2*w-1:0] prod, input uwire [w-1:0] plier, cand, input uwire clk ); localparam int wlog = $clog2(2*w); // cadence translate_off if ( 2**wlog != 2*w ) $fatal(2,"Size, parameter w=%0d, must be a power of 2.\n",w); // cadence translate_on bit [wlog-1:0] pos; always_ff @( posedge clk ) pos <= pos + 1; logic [w-1:0] sum, carry; logic [1:0] sc; always_ff @( posedge clk ) begin for ( int i=0; i BFA(cin) -> // : 1 + 1 + 2 = 4 // !pos_eq_0 && carry[i] -> BFA -| prod[pos] internal mux // : 1 + 4 = 5 // prod[pos] decode network -| prod[pos] internal mux // : 1 + lg w // // Critical Path (the winner is, for w > 4). // plier mux: 2 lg w // // Clock Period: (Register delay: 6) // 6 + 2 lg w // Latency: Based on 2w cycles. // 6w + 2w lg w // /// Comparison with Synthesis `ifdef xxx Module Name Area Period Period Total Init. Target Actual Latency Interv mult_seq_wfront_w8 45390 1000 3132 50112 50112 mult_seq_wfront_w16 89668 1000 3260 104320 104320 mult_seq_wfront_w32 178367 1000 4202 268928 268928 mult_seq_wfront_w64 345415 1000 4716 603648 603648 mult_seq_wfront_opt_w8 47575 1000 2428 38848 38848 mult_seq_wfront_opt_w16 94652 1000 2275 72800 72800 mult_seq_wfront_opt_w32 177706 1000 2546 162944 162944 mult_seq_wfront_opt_w64 345301 1000 2724 348672 348672 mult_seq_stream_w8 44320 1000 4518 36144 36144 mult_seq_stream_w16 78395 1000 8868 141888 141888 mult_seq_stream_w32 153863 1000 16361 523552 523552 mult_seq_stream_w64 304047 1000 30276 1937664 1937664 `endif // // Optimizations reduce period without significant cost impact. // // Cost growth with w agrees with analysis, but period does not grow // as fast as modeled. // // Workfront multiplier much faster than streamlined for w>8 /// Degree-m Workfront Multiplier // module mult_seq_wfront_m #( int w = 16, int m = 2 ) ( output logic [2*w-1:0] prod, input uwire [w-1:0] plier, cand, input uwire clk ); localparam int iterations = ( 2*w + m - 1 ) / m; localparam int iter_lg = $clog2(iterations); localparam int wlog = $clog2(m * iterations); bit [iter_lg-1:0] iter; always_ff @( posedge clk ) iter <= iter + 1; logic [w-1:-1] sum, carry; always_ff @( posedge clk ) begin logic [w-1:-1] j_sum[m+1], j_carry[m+1]; logic [1:0] sc; j_sum[0] = iter ? sum : 0; j_carry[0] = iter ? carry : 0; for ( int j=0; j