본문 바로가기

(작성중입니다)

 

숫자에 대한 verilog 표현에 대해서 알아본다.
원본 소스 글은 여기이다. 제 공부로 적당히 지워가며 정리된 것이니 꼭 원본 글로 방문해서 제대로 된 글을 읽어보시길 바랍니다.
Numbers in Verilog

Binary in Verilog

By default, a Verilog reg or wire is 1 bit wide. This is a scalar:

기본으로 선언은 1 비트 길이를 표현한다.

wire  x;  // 1 bit wire
reg   y;  // also 1 bit
logic z;  // me too!

A vector is declared like this: type [upper:lower] name;

벡터 값은 아래와 같이 표현된다.

wire   [5:0] a;  // 6-bit wire
reg    [7:0] b;  // 8-bit reg
logic [11:0] c;  // 12-bit logic

a, b, and c are vectors:

  • Wire a handles 0-63 inclusive (26 is 64).
  • Reg b handles 0-255 inclusive (28 is 256).
  • Logic c handles 0-4095 inclusive (212 is 4096).

It’s easy to miss the width from a signal declaration and create a scalar by mistake:
아래처럼 실수를 하기 쉽다. 이름만 가지고 할당을 하게 되니, 아래 예제는 11비트가 버려지게 된다.

wire [11:0] x;  // 12 bit (vector)
wire x1;        // 1 bit (scalar)

always_comb x1 = x;  // discards 11 bits!

Alas, many tools provide no warning on width mismatches. To catch issues with bit widths, I strongly recommend you lint your designs with Verilator.

이런 에러는 린트 툴에서 쉽게 발견해 준다. Verilog 린트 툴을 사용하는 것을 권고 한다.

Four State Data Types
The logic, reg, and wire data types can take one of four values: 0, 1, X, Z, where X is unknown, and Z is high impedance. For this introduction to numbers, we’re going to ignore X and Z.

We cover vectors in detail in the next part of the series: Vectors and Arrays.

Literals

보통은 그냥 숫자로 표현하면 정수형이다.
Verilog has several options for interpreting literal numbers.
A lone unadorned number, such as 42 is interpreted as a signed 32-bit decimal:

// signed 32-bit decimal literals
42
123
7
65536

The default interpretation seems reasonable, but sometimes it’s easier to work in another base, and Verilog gives you the option of binary, octal and hexadecimal as well as decimal.

You specify the base (radix) using a single quote followed by a letter:
아래와 같이 radix 를 이용하여 2진수, 16진수를 표현할 수 있다.

  • b - binary
  • o - octal
  • d - decimal
  • h - hexadecimal
// 32-bit wide literals with decimal value 9
'b1001  // unsigned binary
'd9     // unsigned decimal
'h9     // unsigned hexadecimal
'o11    // unsigned octal

With hexadecimal, you can use the letters a-f and A-F as well as the usual digits:

// 32-bit wide hexadecimal literals
'hA       // unsigned 10 in decimal
'h3C      // unsigned 60 in decimal
'hfa      // unsigned 250 in decimal
'hFFCC00  // bright yellow hexadecimal colour

Unlike the default decimal interpretation, these literals are unsigned, even with a decimal base.
앞에 's'를 붙이게 되면 singed 표현이 가능하다.

If you want a signed literal with a base, you need to add an s before the base:

// 32-bit wide with decimal value 9
'sb1001  // signed binary
'sd9     // signed decimal
'sh9     // signed hexadecimal
'so11    // signed octal

You can use a negative sign to create a negative literal. Stick with decimal for literals with minus signs: it quickly gets confusing in other bases.

// signed 32-bit wide decimal literals
-42
-1
-65536
-25_000_000  // negative 25 million (underscores for readability)

We’ll be covering signed numbers in detail in the next section.

You specify a literal width in bits by putting it before the single quote:

// 12-bit wide with decimal value 1024
12'b0100_0000_0000  // unsigned binary literal (underscores for readability)
12'd1024            // unsigned decimal literal
12'sh400            // signed hex literal
12'o2000            // unsigned octal literal

ProTip: You can include underscores in your literals to improve readability.

Zero Fill

What happens if you only specify some of the bits in a literal?
비트가 맞지 않으면, 앞쪽은 전체 0으로 채워진다.
아래 2 예제는 같은 값을 가진다.

8'b00001111;  // unsigned 00001111 (decimal 15)
8'b1111;      // unsigned 00001111 (decimal 15)

Verilog filled the remaining bits of 8'b1111 with zero, which is what you might expect and is how numbers usually work day-to-day.

It doesn’t matter if the literal is signed or another base; the value is always filled with zeros:

Signed 숫자 값도 0으로 채워진다.

8'hA;         // unsigned 00001010 (decimal 10)
8'sb1010;     // signed   00001010 (decimal 10)

In case you’re wondering about sign extension, fear not we come to that later in this post.

Literal Tricks

In SystemVerilog, you can set all the bits of a vector to ‘1’:

SystemVerilog 에서는 전체 1를 채워주는 아주 좋은 예제가 있다. 아래처럼 입력하면 전체 하나의 숫자로 채워진다. 즉, 아래 2개의 예제는 같은 값을 가진다.

// x and y have the same value:
reg [11:0] x = '1;
reg [11:0] y = 12'b1111_1111_1111;

You can also use the concat operator {} to set specific patterns in SystemVerilog and Verilog:

SystemVerilog and Verilog 2가지 언어에 공통적으로 사용되는 패턴은 {} 이다. 비트별로 합치는 경우에 사용된다.
12{1'b1} <- 이렇게 하면 12비트가 1인 것을 나타낸다.
{ , , } 컴마를 이용해서 비트별로 각각 지정할 수 있다.

localparam CORDW = 12;  // coordinate width in bits

reg [CORDW-1:0] x = {CORDW{1'b1}};  // x = 1111_1111_1111

reg [CORDW-1:0] y = { {1'b1}, {CORDW-1{1'b0}} };  // y = 1000_0000_0000

You can nest concat operators, as in the final example above.

Concat allows us to set an appropriate value regardless of the vector width.

Signed Numbers

If your design requires negative values, you need to handle signed numbers. The standard approach is two’s complement, as with almost all CPUs and software.

음수는 2의 보수 값으로 나타낸다.

The Two’s Complement

With two’s complement, addition, subtraction, and multiplication all work as they do with positive binary numbers. But what is the two’s complement? The positive and negative two’s complement representations of an N-bit number add up to 2N.

For example, with four-bit values: 7 is 0111 and -7 is 1001 because 0111 + 1001 = 10000 (24).

Discarding the extra bit, the result of adding a number and its two’s complement is always zero.

You can switch the sign of a two’s complement number by inverting the bits and adding one:

음수 만드는 단계별 설명이니, 참고하자.
인버터 하고, 꼭 더하기 1
MSB는 부호 용도으로 남겨두길...

Start:      0111    (decimal +7)
Invert:     1000
Add 1:      0001
Result:     1001    (decimal -7)

Start:      1001    (decimal -7)
Invert:     0110
Add 1:      0001
Result:     0111    (decimal +7)

You rarely need to determine the two’s complement; Verilog can handle it for you.

Let’s look at a few additions to confirm things work as expected:

  0110      +6
+ 1101      -3
= 0011      +3

  1001      -7
+ 0011      +3
= 1100      -4

  1001      -7
+ 0111      +7
= 0000       0

To learn more, check out the Wikipedia article on two’s complement.

Range

The range of a two’s complement vector of width n is:

-2(n-1) to +2(n-1)-1

For example, an 8-bit signed vector has the range:

-27 to +27-1 = -128 to +127

You can’t represent +128 with an 8-bit signed vector.

Try to take the two’s complement of -128:

Start:      1000_0000   (decimal -128)
Invert:     0111_1111
Add 1:      0000_0001
Result:     1000_0000   (decimal -128)

You get the original -128 back.

Signing Your Signals

Declaring a vector as signed is easy:

벡터 변수 선언은 다음과 같이 한다. reg 뒤에 signed 를 붙이면 된다.

reg        [7:0] u;  // unsigned (0..255)
reg signed [7:0] s;  // signed   (-128..127)

For signed literals, as we discussed above, you add the s prefix to the base:

부호는 제일 앞에 쓰도록 한다. + 는 굳이 쓸 필요없다.

reg signed [7:0] x = -8'sd55;  // x position: -55 (signed)
reg signed [7:0] y =  8'sd32;  // y position: +32 (signed)

Test Negative

It’s straightforward to check the sign of a number with two’s complement:

  • For positive numbers (and zero), the most significant bit is 0
  • For negative numbers, the most significant bit is 1

You can check the most significant bit directly:

reg signed [7:0] s;  // 8 bit signed vector

always_ff @(posedge clk) begin
    if (s[7]) begin  // ?! intent unclear
        // s is negative
    end else begin
        // s is positive or zero
    end
end

However, comparison operators are usually clearer:

reg signed [7:0] s;  // 8 bit signed vector

always_ff @(posedge clk) begin
    if (s < 0) begin
        // s is negative
    end else begin
        // s is positive or zero
    end
end

Signed Expressions

Now we know how to handle signed vectors and literals, it’s time to wrestle with expressions. I’ve done my best to accurately distil a rather dry and complex subject into something palatable, so I hope I don’t offend the language lawyers and bore everyone else.

An expression consists of operands, such as variables and literals, and operators, such as addition and assignment.

Verilog uses the width of the widest operand when evaluating an expression.

It doesn’t matter what the operators are; all Verilog cares about is the width of the operands.

Narrower operands are widened until they’re the same width as the widest. For unsigned operands, Verilog simply fills the new bits with zero, but with signed operands, it uses sign extension.

Sign extension copies the most significant bit (MSB) to fill the width. Remember, for a signed number, the MSB is 1 for negative numbers and 0 otherwise.

This doesn’t sound too bad until you learn that in Verilog:

If all the operands are signed, the result is signed. Otherwise, it’s unsigned.

Verilog doesn’t consider it an error to mix signed and unsigned operands; it treats them all as unsigned. This leads to painful surprises that can be hard to debug.

Take a look at this example:

module wider_tb ();
    logic signed [7:0] x, y;  // signed 8 bits wide
    logic signed [7:0] s;  // signed 8 bits wide
    logic        [7:0] u;  // unsigned 8 bits wide
    logic signed [3:0] m;  // signed 4 bits wide

    always_comb begin
        x = s + m;  // signed + signed
        y = u + m;  // unsigned + signed
    end
    initial begin
        #10
        s = 8'sb0000_0111;  // decimal 7
        u = 8'b0000_0111;   // decimal 7
        #10
        $display(" s: %b  %d", s, s);
        $display(" u: %b  %d", u, u);


        #10
        m = 4;  // decimal 4
        $display("When 'm' is +4:");
        $display("m:     %b  %d", m, m);
        $display("x: %b  %d", x, x);
        $display("y: %b  %d", y, y);

        #10
        m = -4;  // decimal -4
        $display("When 'm' is -4:");
        $display("m:     %b  %d", m, m);
        $display("x: %b  %d", x, x);
        $display("y: %b  %d  **SURPRISE**", y, y);
    end
endmodule

Running the wider test bench:

s: 00000111     7
u: 00000111     7

When 'm' is +4:
m:     0100     4
x: 00001011    11 
y: 00001011    11

When 'm' is -4:
m:     1100    -4
x: 00000011     3
y: 00010011    19  **SURPRISE**

Looking at the binary, we can understand where 19 comes from.

When we set m = -4 it has binary value 1100.

When Verilog evaluates the expression y = u + m:

  1. m is 4 bits wide, but u and y are 8 bits
  2. m must be widened to 8 bits to match the widest operands
  3. u is unsigned, so m is also considered unsigned
  4. Treated as unsigned, m is widened to 8 bits with zeros: 00001100 (12 in decimal)
  5. 00001100 + 00000111 = 00010011 (12+7=19 in decimal)

If you take one thing away from this post:

Never mix signed and unsigned variables in one expression!

Truncated

If the left-hand side of an assignment is smaller than the right-hand side, then the value is truncated. The following examples use literals, but this also applies to expressions and variables.

wire [3:0] a = 15;           // a gets the value 1111
wire [3:0] b = 8'b10001010;  // b gets the value 1010
wire [3:0] c = 12'hF0F;      // c gets the value 1111

Because 15 has no base, Verilog treats it as a signed 32-bit decimal:

0000_0000_0000_0000_0000_0000_0000_1111

For a, the truncated bits are all zero, so don’t change the value.

If the right-hand side is signed, truncating it may change its value and sign:

wire signed [3:0] d = 8'b11111100;  // d gets the value 1100 (-4)
wire signed [3:0] e = 8'b11110011;  // e gets the value 0011 (-13 becomes +3)

We assigned -13 to e, but after truncation, we get +3.

Your synthesis tool or simulator should warn you about truncated values.

Reckoning with Arithmetic

We’ve talked a lot about the representation of numbers, but we’ve yet to do much maths. Let’s finish this post by having a quick look at the elementary arithmetic operations.

Addition

Modern FPGAs include dedicated logic, such as carry chains, to handle addition efficiently: there’s no need to roll your own design. However, there are plenty of university slide decks online if you want to create an adder from scratch.

Before we skip over addition entirely, it’s worth having a quick look at overflow.

Consider the following:

wire [3:0] x, y, z;

always_comb z = x + y;

The signals x y z are unsigned and 4 bits wide.

If we set x = 2 and y = 9 then (x + y) = 11 or 10112 in binary.

z has the value 10112: z = 11 as expected.

If we set x = 11 and y = 7, you might expect the result to be (x + y) = 18 or 100102 in binary, which is then truncated to fit in z.

However, Verilog uses the width of the widest operand to evaluate expressions. The operands in our case are the three variables x, y, and z, all of which are 4 bits wide. Thus the result of the expression is 00102, and it’s assigned to z.

Modular arithmatic, where the result wraps around, is the norm in hardware and software.

Catching the Overflow

However, there are times when you want to know if overflow has occurred:

wire [3:0] x, y, z;
wire c;  // carry bit

always_comb {c, z} = x + y;

The widest operand in our new expression is {c, z}, which is 5 bits wide, so when we evaluate x + y, we get 100102. The value of 1 gets assigned to c while 00102 is assigned to z as before.

You could use the carry bit to set an overflow flag when designing a CPU.

Another potential use is with saturation arithmetic: we keep z at its maximum value when overflow occurs:

wire [3:0] x, y, z;
wire c;  // carry bit

always_comb begin
    {c, z} = x + y;
    if (c) z = 4'b1111;  // 15: the maximum value z can hold
end

With this saturation design, if x = 11 and y = 7, then z = 15.

Learn more from saturation arithmetic on Wikipedia.

Subtraction

Subtraction is almost the same as addition: subtracting y is equivalent to adding -y. It’s easy to find -y, as we saw earlier when discussing two’s complement.

Consider the following:

wire [3:0] x, y, z;

always_comb z = x - y;

If we set x = 11 and y = 7:

Find -y:
    Start:  0111    (decimal +7)
    Invert: 1000
    Add 1:  0001
    Result: 1001    (decimal -7)

Add x and -y:
      1011    +11  (x)
    + 1001    - 7  (y)
    = 0100    + 4  (z)

Your synthesis tool will handle this, but remember that subtraction is a little more complex than addition. Prefer incrementing over decrementing and register the results of subtractions before using them in subsequent calculations, especially on low-power FPGAs such as the iCE40UP.

Multiplication

Multiplication is more complex than addition or subtraction, but modern FPGAs can handle it with dedicated DSP blocks. Small vectors don’t require too much thought, but the resulting output has potentially twice as many bits as the inputs:

wire [3:0] x;
wire [3:0] y;
wire [7:0] z;  // product (twice as wide)

always_comb z = x * y;

If we set x = 11 and y = 7:

Multiply x and y:
          1011  +11  (x)
    x     0111  + 7  (y)
    = 01001101  +77  (z)

I’ve written a dedicated post on Multiplication with FPGA DSPs that looks at the use of reg and pipelining to improve multiplication performance and minimise logic use.

Division

What about division? I’ve bad news: Verilog won’t synthesise this for you. The good news is that it’s not hard to implement yourself: I have a dedicated post on Division in Verilog that covers integers, fixed-point, and signed numbers.

 

숫자 표현 십진수가 가장 쉬워

B로그0간

개발 관련 글과 유용한 정보를 공유하는 공간입니다.