(작성중입니다)
숫자에 대한 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
, whereX
is unknown, andZ
is high impedance. For this introduction to numbers, we’re going to ignoreX
andZ
.
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
:
m
is 4 bits wide, butu
andy
are 8 bitsm
must be widened to 8 bits to match the widest operandsu
is unsigned, som
is also considered unsigned- Treated as unsigned,
m
is widened to 8 bits with zeros:00001100
(12 in decimal) 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.
'IT > ASIC | FPGA' 카테고리의 다른 글
Xilinx – PROM mcs 설정 – spi buswidth clock 조정 (0) | 2024.02.22 |
---|---|
Verilog - Parameter, deparam 사용법 (0) | 2024.02.20 |
Xilinx Vivado - SPRAM, DPRAM, block RAM 활용 (1) | 2024.02.20 |
Verilog - Metastable, CDC (clock domain crossing) 정리 (0) | 2024.02.16 |
Verilog - 합성에 유리한 coding 스타일 (2) | 2024.02.16 |