# EDN Access--03.14.97 Parameterized multiplier aids early simulations

-March 14, 1997

March 14, 1997

Parameterized multiplier aids early simulations
Tom Balph and Alex Chwu, Motorola, Tempe, AZ

Listing 1 presents a parameterized Verilog behavioral model (not synthesizable) of a 2's complement multiplier that you can easily drop into your system for initial behavioral simulations and for later verification of actual hardware designs. This behavioral multiplier model is intended for initial, fast simulation but not for synthesis. Synthesizable multipliers tend to slow the simulation. This model is most valuable for early system simulations, for evaluating multiplier requirements, and for checking results against the real synthesized multiplier. You can download Listing 1 from EDN's Web site: www.ednmag.com. At the registered-user area, go into the "Software Center" to download the file from DI-SIG, #1999.

Two's complement multiplication presents a problem for Verilog HDL because there is no simple, direct way of writing the equivalent mathematical function that provides a correct result for all input values. This model is valuable because it provides a correct 2's complement multiply in all cases for parameterized bit widths of the two-input operands (multiplier and multiplicand) and the product, plus a variety of applied rounding/truncation choices for the product. The options available with this model can be useful when you are evaluating the needs of a signal-processing application.

A brief review of 2's complement notation helps you understand the unique nature of a 2's complement multiplier. For a 2's complement number, the MSB always carries a negative weight and also serves as the sign bit. A 2's complement fractional number of, say, 4 bits, A1 through A4, has a value of -A1*20+A2*2-1+A3*2-2+A4*2-3. For example, 0.111 binary has the value of 0.875 decimal, and 1.001 has the value of -0.875. Also, note that you perform sign inversion of a 2's complement number by first taking the 1's complement of the number (1's complement of 1001 is 0110) and then adding 1 to the LSB (0110+1=0111).

Before showing how the model handles varying bit widths, some additional background material is helpful. For an M-bitxN-bit multiply, an (M+N)-bit product results. No difference exists between a 2's complement integer and fractional-multiply products except for the location of the binary point. For an integer multiply, the values are right-justified; that is, the binary point follows the LSB in both the operands and the product. For fractional or mixed binary-weight numbers, the product's binary point sits immediately to the left of the position that's equal to the sum of the bit positions to the right of the binary points in the operands. A simple example can help explain this concept; if you multiply an 8-bit number, A, by a 6-bit number, B, that has the following binary-point locations, the position of the product's binary point is as follows:

AAAA.AAAA
x BBBB.BB
_________________
ZZZZZZZZ.ZZZZZZ

Remember that for a 2's complement number, the MSB always carries a negative weight. This feature can cause confusion in fractional notation in which the binary point sits immediately to the right of the sign bit. If you modify the initial example for fractional notation, the result is as follows:

A.AAAAAAA

x B.BBBBB

_______________________

Z1Z2.Z3Z4Z5ZiZZZZZZZZ

The product's binary point now sits to the right of the second MSB. With the operands, the MSB carries a binary weight of -1, whereas, with the product, the MSB carries a binary weight of -2, and the next most significant bit (NMSB) immediately to the left of the binary point carries a weight of +1. Z has the value of -Z1*21+Z2*20+Z3*2-1+Z4*2-2+Z5*2-3+...

Commonly, you perform a fractional square multiply (M-bitxM-bit) with the product rounded to an M-bit value. In this case, common practice is to use the NMSB (rather than the MSB) plus the M-1 bits immediately to the right of the product's binary point. This practice works because the NMSB equals the MSB for all product values except when both operands equal -1 (-1x-1=1), which produces a product with an MSB=0 and an NMSB=+1. The result is correct, but an overflow condition exists for fractional notation. You can easily detect an overflow for fractional notation using an XOR function of the MSB and NMSB.

The actual model in Listing 1 divides the multiplier code into three major sections: the multiplier core, rounding, and overflow check/protection. Note that the full-bit-width product passes from the core to the next two sections, and, if the final output bit width is smaller, the code at the last stage of this module assumes that the truncated/round-off bits are the defined LSBs.

In Verilog HDL, you can behaviorally perform a simple magnitude multiply using just the multiply operator "*," for example, A*B. However, the 2's complement case is more complex because of the negative binary weight of the operand sign bits, and Verilog provides no special multiply operator for 2's complement. The model's core algorithm overcomes this problem by converting negative numbers to equivalent positive values performing the multiply, and, finally, correcting the product as the sign bits require.

The core section also handles the special cases in which either of the input operands is negatively limited. A number is negatively limited when the MSB is 1 and the remaining bits are 0. The result is the most negative number that has no equivalent positive value (its 2's complement goes to 0). The core provides the correct product for these cases in which one or both of the operands is the largest negative value.

Often, the datapath narrows in bit width, or downstream calculations don't use the full product. As a result, the code immediately rounds or simply truncates the product after the multiply. The code section following the core offers simple truncation or three ways of rounding: convergent, 2's complement, and up-magnitude. Truncation is simply not using the unneeded lower product bits.

Rounding is more complex, but, of the different types of rounding, 2's complement rounding is the easiest to understand. If the truncated product bits--say, the lowest 8 bits-- are less than the hexadecimal value of \$80, the code truncates just the upper product; if they are more than the hexadecimal value, the code rounds up the upper product by one.

For convergent rounding, the result is the same as 2's complement rounding, except when the value is exactly \$80; if the LSB of the preserved bits is 0, truncation is implied; if the LSB is 1, then round up.

For up-magnitude rounding, the product sign bit is the deciding factor for the case of \$80. If the product sign is 0 (positive), then round up; if the product sign is 1 or negative, simply truncate. This process has the effect of rounding the number toward the larger magnitude. This rounding algorithm is useful when maintaining the largest magnitude for D/A conversion. Remember that for negative 2's complement fractional numbers, truncation of the end bits increases the magnitude. You use this type of rounding in recent standards, such as ISO/IEC 11172-3 MPEG Audio.

The last section of the model checks the 2 sign bits for overflow conditions due to the multiply or rounding. Here, the model assumes 2's complement fractional numbers and preserves only one of the 2 sign bits in the output. If the sign bits are not both 0s or both 1s, data overflow has occurred, and the value needs to be saturated to the valid positive or negative limits. The last step grabs the appropriate bits, as defined by the parameters, to be the module output.

You can use the "defparam" statement in another top-level module to reassign the parameter values per module instantiation. The last section, overflow logic, assumes fractional numbers, that is, the binary point immediately follows the MSB. For mixed-weight or integer numbers and depending on the desired output bits from the product (that is, scaling), you may have to change the overflow section. (DI #1999)

 Listing 1 --Parameterized HDL 2's complement multiplier model module mult (out, in1, in2); //---------------------------------------//// Declare parameter values//parameter size1 = 24; // size of in1parameter size2 = 24; // size of in2parameter sizet = 24; // size of outparameter round = 1; // 1 for two's complement// 2 for convergent// 3 for divergent /up-mag// else simple truncation//---------------------------------------//// Input/output ports//output [sizet-1:0] out;input [size1-1:0] in1;input [size2-1:0] in2;//---------------------------------------//// Internal signals//wire sign;wire [size1-1:0] x;wire [size2-1:0] y;reg [size1+size2-1:0] z;wire [size1+size2-1:0] z_in1_minus;wire [size1+size2-1:0] z_in2_minus;wire [size1+size2-1:0] product;wire [size1+size2-1:0] product_add;reg [size1+size2-1:0] product_round;reg [size1+size2-1:0] product_protect;//---------------------------------------//// Parameter derived constants//wire [size1-1:0] in1_minus_limit = {1'b1, {(size1-1){1'b0}}}; // 100000..wire [size2-1:0] in2_minus_limit = {1'b1, {(size2-1){1'b0}}}; // 100000..wire [size1+size2-1:0] z_plus_over = {1'b0, 1'b1, {(size1+size2-2){1'b0}}}; // 010000..wire [size1+size2-1:0] z_plus_limit = {2'b0, {(size1+size2-2){1'b1}}}; // 001111..wire [size1+size2-1:0] z_minus_limit = {2'b11, {(size1+size2-2){1'b0}}}; // 110000..wire [size1+size2-1:0] round_bit = {{(sizet+1){1'b0}}, 1'b1, {(size1+size2-sizet-2){1'b0}}};wire [size1+size2-sizet-2:0] trunc_bits = { 1'b1, {(size1+size2-sizet-2){1'b0}}};//---------------------------------------//// Multiplier core / representative of actual logic//assign sign = in1[size1-1] ^ in2[size2-1];assign x = (in1[size1-1]) ? {1'b0, ~in1[size1-2:0] + 1'b1} : in1; // magnitude of in1assign y = (in2[size2-1]) ? {1'b0, ~in2[size2-2:0] + 1'b1} : in2; // magnitude of in2assign z_in1_minus = {~in1[size1-1], ~in1 + 1'b1, {(size1+size2-sizet-1){1'b0}}};assign z_in2_minus = {~in2[size2-1], ~in2 + 1'b1, {(size1+size2-sizet-1){1'b0}}};always @(in1 or in2 or x or y or z_in1_minus or z_in2_minus or sign) beginif ((in1 == in1_minus_limit) && (in2 == in2_minus_limit)) // in1 = in2 = 10000..z &= z_plus_over;else if (in1 == in1_minus_limit) // in1 = 10000..z &= z_in2_minus;else if (in2 == in2_minus_limit) // in2 = 10000..z &= z_in1_minus;else if (sign) // in1 * in2 & 0z &= ~(x * y) + 1'b1;else // in1 * in2 >= 0z &= x * y;endassign product = z;//---------------------------------------//// Rounding logic//assign product_add = product + round_bit;always @(product or product_add) beginif (round == 1)product_round &= product_add;else if (round == 2)if (product[size1+size2-sizet-2:0] == trunc_bits)product_round &= product[size1+size2-sizet-1] ? product_add : product;elseproduct_round &= product_add;else if (round == 3)if (product[size1+size2-sizet-2:0] == trunc_bits)product_round &= product[size1+size2-1] ? product : product_add;elseproduct_round &= product_add;elseproduct_round &= product;end//---------------------------------------//// Overflow logic//always @(product_round) begincase (product_round[size1+size2-1:size1+size2-2])2'b00: product_protect &= product_round;2'b01: product_protect &= z_plus_limit;2'b10: product_protect &= z_minus_limit; // n/a2'b11: product_protect &= product_round;endcaseendassign out = product_protect[size1+size2-2:size1+size2-sizet-1];endmodule