本部分將不再介紹Vivado工程的整體流程,將主要精力放在代碼上面,具體的流程可參考:https://blog.csdn.net/crodemese/article/details/130438348
本部分代碼也已上傳到github:https://github.com/linxunxr/VerilogStudy
1. 全加器
那么什么是全加器呢?我們都知道加法,即1+1=2,當(dāng)個(gè)位數(shù)相加大于9時(shí)就需要進(jìn)位。在二進(jìn)制中也是如此,因此,一位二進(jìn)制的相加的真值表便如下圖:
a | b | sum | count |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 0 | 1 |
圖中a、b為輸出,sum為相加的結(jié)果,count為進(jìn)位,即當(dāng)輸入都為1時(shí),相加的結(jié)果為0,進(jìn)位信號(hào)輸出1。這就是一個(gè)一位半加器。而全加器就是在此基礎(chǔ)上再加一個(gè)輸入,這個(gè)輸入用來獲取前一個(gè)的進(jìn)位信號(hào),其真值表如下圖:
count | a | b | sum | count |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 1 | 0 |
0 | 1 | 0 | 1 | 0 |
0 | 1 | 1 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
1 | 0 | 1 | 0 | 1 |
1 | 1 | 0 | 0 | 1 |
1 | 1 | 1 | 1 | 1 |
這就是一個(gè)全加器的真值表。
2. 設(shè)計(jì)思路
我們都知道Verilog最大的特點(diǎn)就是自頂向下的設(shè)計(jì)方式,因此我們嘗試對(duì)上面的全加器進(jìn)行模塊劃分,即使用兩個(gè)半加器和一個(gè)或門來實(shí)現(xiàn),其模塊如下圖:
這樣就可以實(shí)現(xiàn)一個(gè)一位全加器。
3. 模塊化實(shí)現(xiàn)
- 首先我們來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的或門模塊,其具有兩輸入一輸出,因此在
module
命名中對(duì)其定義輸入a b
輸出c
,然后將a
和b
進(jìn)行或運(yùn)算并賦值給c
,如下:
// 或門模塊
module or_gate (
input a,
input b,
output c
);
// 或運(yùn)算
assign c = a | b;
endmodule
- 接下來我們來實(shí)現(xiàn)較為復(fù)雜的半加器,其具有兩輸入兩輸出,通過我們上面對(duì)半加器真值表的觀察可知,輸出的相加信號(hào)可視為兩輸入信號(hào)的異或運(yùn)算,輸出的進(jìn)位信號(hào)可視為兩輸入信號(hào)的與運(yùn)算,因此實(shí)現(xiàn)如下:
// 半加器模塊
module half_adder (
input a,
input b,
output sum,
output carry
);
// 異或運(yùn)算完成相加
assign sum = a ^ b;
// 與運(yùn)算完成進(jìn)位
assign carry = a & b;
endmodule
- 接下來就是根據(jù)設(shè)計(jì)思路通過對(duì)半加器模塊和或門模塊的引用來完成一位全加器的頂層模塊設(shè)計(jì):
- 其基本語法仍然是賦值語句,只不過多了運(yùn)算符的操作,與C語言類似,Verilog也有多種運(yùn)算符(默認(rèn)具有C語言基礎(chǔ),僅對(duì)與C語言不同的進(jìn)行解釋):
- 算數(shù)運(yùn)算符:
-
+
:加法運(yùn)算符,或正值運(yùn)算符 -
-
:減法運(yùn)算符,或負(fù)值運(yùn)算符 -
*
:乘法運(yùn)算符 -
/
:除法運(yùn)算符 -
%
:模運(yùn)算符
-
- 位運(yùn)算符:
-
~
:取反 -
&
:按位與 -
|
:按位或 -
^
:按位異或 -
^~
:按位同或
-
- 邏輯運(yùn)算符:
-
&&
:邏輯與 -
||
:邏輯或 -
!
:邏輯非
-
- 關(guān)系運(yùn)算符:
-
<
:小于 -
>
:大于 -
<=
:小于等于 -
>=
:大于等于
-
- 等式運(yùn)算符:
-
==
:等于 -
!=
:不等于 -
===
:等于,與上面的等于不同的是,這里的等于對(duì)兩個(gè)操作數(shù)的要求是嚴(yán)格的,即對(duì)不定值x
和高阻值z
也會(huì)進(jìn)行比較,只有兩端嚴(yán)格相等時(shí)其結(jié)果才為1
,因此其結(jié)果不會(huì)出現(xiàn)不定值x
和高阻值z
,下面的不等于類似。 -
!==
:不等于
-
- 移位運(yùn)算符:
-
<<
:左移運(yùn)算符 -
>>
:右移運(yùn)算符
-
- 位拼接運(yùn)算符:
-
{}
使用大括號(hào)可以將兩個(gè)或多個(gè)信號(hào)的某些位拼接起來進(jìn)行運(yùn)算操作,其使用方法如下:{信號(hào)1的某幾位,信號(hào)2的某幾位,...,信號(hào)n的某幾位}
- 如果我們需要將
a
信號(hào)和b
信號(hào)進(jìn)行拼接,可用{a,b}
來進(jìn)行操作 - 除此之外,還可以通過重復(fù)發(fā)來進(jìn)行簡(jiǎn)化操作,如
{4{w}}
其相當(dāng)于{w,w,w,w}
- 可以通過嵌套的方式來表達(dá),如
{b,{3{a,b}}}
其相當(dāng)于{b,a,b,a,b,a,b}
-
- 縮減運(yùn)算符:縮減運(yùn)算符是單目運(yùn)算符,其也有與、或、非運(yùn)算,但是與位運(yùn)算不同的是,縮減運(yùn)算符是對(duì)單個(gè)操作數(shù)進(jìn)行或、與、非運(yùn)算,最后的結(jié)果為1位的二進(jìn)制數(shù),如與運(yùn)算:
- 算數(shù)運(yùn)算符:
// 全加器模塊
module full_adder (
input a,
input b,
input cin,
output sum,
output cout
);
// 對(duì)中間的變量進(jìn)行定義
wire s1, c1, c2;
// 對(duì)半加器的實(shí)例化
half_adder half_adder1(a, b, s1, c1);
half_adder half_adder2(s1, cin, sum, c2);
// 對(duì)或門的實(shí)例化
or_gate or_gate1(c1, c2, cout);
endmodule
reg [3:0] B;
reg C;
// 對(duì)B進(jìn)行縮減與運(yùn)算,其運(yùn)算過程相當(dāng)于 C = ((B[0] & B[1]) & B[2]) & B[3]
C = &B;
- 除此之外我們還需要對(duì)Verilog的數(shù)據(jù)類型有一定的了解:
- 常量
- 數(shù)字:
- 整數(shù):
- 二進(jìn)制整數(shù):
b或B
- 八進(jìn)制整數(shù):
o或O
- 十進(jìn)制整數(shù):
d或D
- 十六進(jìn)制整數(shù):
h或H
- 其表示方法有三種:
- <位寬><進(jìn)制><數(shù)字>,如
2'b00
就是一個(gè)兩位的二進(jìn)制數(shù)字00
- <進(jìn)制><數(shù)字>,在這種情況下,數(shù)字的位寬采用默認(rèn)位寬(由機(jī)器系統(tǒng)決定),最少為32位,因此不推薦
- <數(shù)字>,在這種描述方式中,采用默認(rèn)進(jìn)制(十進(jìn)制),但是描述不清,一般仍采用第一種表示方法
- <位寬><進(jìn)制><數(shù)字>,如
- 二進(jìn)制整數(shù):
-
x
和z
值:數(shù)電中,x
代表不定值,z
值代表高阻值。如:-
4'b10x0
表示4位的二進(jìn)制數(shù)從地位數(shù)起的第二位為不定值 -
12'dz或12'd?
表示12為十進(jìn)制數(shù),其值為高阻值 -
8'h4x
表示8位十六進(jìn)制,其低4位為不定值
-
- 負(fù)數(shù):在位寬表達(dá)式前加上一個(gè)減號(hào),如:
-8'd5
- 下劃線:可以具體的數(shù)字之間分隔開來以提高程序可讀性,如:
16'b1010_0000_1111_0101
- 整數(shù):
- 參數(shù):即用
parameter
定義一個(gè)標(biāo)識(shí)符代表一個(gè)常量,以提高程序可讀性和可維護(hù)性,常用于定義延遲時(shí)間和變量寬度。如:parameter msb = 7;
定義參數(shù)msb
為常量7。
- 數(shù)字:
- 變量:
-
wire
型:常用來表示用以assign
關(guān)鍵字指定的組合邏輯信號(hào),Verilog模塊中的輸入和輸出信號(hào)默認(rèn)自動(dòng)定義為wire
型??捎米鋈魏畏匠淌降妮斎耄部梢杂米?code>assign語句或?qū)嵗妮敵?。其定義格式如下:wire [n-1:0] 數(shù)據(jù)名1,數(shù)據(jù)名2,...,數(shù)據(jù)名i
,其表示共有i條總線,每條總線內(nèi)有n條線路 -
reg
型:即寄存器數(shù)據(jù)類型,通過賦值語句可改變寄存器存儲(chǔ)的值,其初始值為不定值x,因此在使用reg
型時(shí)一般需要賦初值,否則在仿真中會(huì)出現(xiàn)不定值x,無法通過仿真確認(rèn)其結(jié)果。同時(shí)reg
型只表示被定義的信號(hào)將用在always
模塊,在always
模塊內(nèi)被賦值的每一個(gè)信號(hào)都必須被定義成reg
型。其定義格式如下:reg [n-1:0] 數(shù)據(jù)名1,數(shù)據(jù)名2,...,數(shù)據(jù)名i
。 -
memory
型:通過對(duì)reg
型變量建立數(shù)組來對(duì)存儲(chǔ)器建模,可以描述RAM型存儲(chǔ)器、ROM存儲(chǔ)器和reg文件。數(shù)組中的每一個(gè)單元通過一個(gè)數(shù)組索引進(jìn)行尋址。其定義格式如下:reg [n-1:0] 存儲(chǔ)器名[m-1:0];
-
- 常量
4.仿真文件
在生成bit文件下載到板子之前,我們一般會(huì)通過編寫仿真文件對(duì)已完成的模塊進(jìn)行驗(yàn)證,其仿真文件如下:文章來源:http://www.zghlxwxcb.cn/news/detail-713818.html
module full_adder_test_top;
// 定義輸入到全加器的信號(hào)
reg a_test, b_test, cin_test;
// 定義從全加器輸出的信號(hào)
wire sum_test, cout_test;
// 將輸入信號(hào)和輸出信號(hào)與全加器相連接
full_adder full_adder_test(
.a(a_test),
.b(b_test),
.cin(cin_test),
.sum(sum_test),
.cout(cout_test)
);
// 開始仿真
initial begin
// 初識(shí)時(shí)刻為0,對(duì)各個(gè)輸入信號(hào)進(jìn)行初始化
a_test = 0;
b_test = 0;
cin_test = 0;
// 過了20ns后改變信號(hào)值
#20 a_test = 1;
b_test = 0;
cin_test = 0;
#20 a_test = 1;
b_test = 1;
cin_test = 0;
#20 a_test = 1;
b_test = 1;
cin_test = 1;
#20 a_test = 0;
b_test = 0;
cin_test = 1;
// 調(diào)用系統(tǒng)函數(shù)使仿真停止
#20 $stop;
end
endmodule
從這里我們看到一般仿真文件的編寫規(guī)則:首先,仿真模塊無輸入無輸出;第二,定義仿真的全加器輸入信號(hào)一般使用reg型,輸出一般使用wire型;第三,使用initial塊確定仿真過程中的各種輸入值的初始化以及賦值;第四,其基本單位為ns?!?highlight: github
本部分將不再介紹Vivado工程的整體流程,將主要精力放在代碼上面,具體的流程可參考:Verilog學(xué)習(xí)一: 控制LED燈的亮滅 - 掘金 (juejin.cn)
1. 全加器
那么什么是全加器呢?我們都知道加法,即1+1=2,當(dāng)個(gè)位數(shù)相加大于9時(shí)就需要進(jìn)位。在二進(jìn)制中也是如此,因此,一位二進(jìn)制的相加的真值表便如下圖:
a | b | sum | count |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 0 | 1 |
圖中a、b為輸出,sum為相加的結(jié)果,count為進(jìn)位,即當(dāng)輸入都為1時(shí),相加的結(jié)果為0,進(jìn)位信號(hào)輸出1。這就是一個(gè)一位半加器。而全加器就是在此基礎(chǔ)上再加一個(gè)輸入,這個(gè)輸入用來獲取前一個(gè)的進(jìn)位信號(hào),其真值表如下圖:
count | a | b | sum | count |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 1 | 0 |
0 | 1 | 0 | 1 | 0 |
0 | 1 | 1 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
1 | 0 | 1 | 0 | 1 |
1 | 1 | 0 | 0 | 1 |
1 | 1 | 1 | 1 | 1 |
這就是一個(gè)全加器的真值表。
2. 設(shè)計(jì)思路
我們都知道Verilog最大的特點(diǎn)就是自頂向下的設(shè)計(jì)方式,因此我們嘗試對(duì)上面的全加器進(jìn)行模塊劃分,即使用兩個(gè)半加器和一個(gè)或門來實(shí)現(xiàn),其模塊如下圖:
這樣就可以實(shí)現(xiàn)一個(gè)一位全加器。
3. 模塊化實(shí)現(xiàn)
- 首先我們來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的或門模塊,其具有兩輸入一輸出,因此在
module
命名中對(duì)其定義輸入a b
輸出c
,然后將a
和b
進(jìn)行或運(yùn)算并賦值給c
,如下:
// 或門模塊
module or_gate (
input a,
input b,
output c
);
// 或運(yùn)算
assign c = a | b;
endmodule
- 接下來我們來實(shí)現(xiàn)較為復(fù)雜的半加器,其具有兩輸入兩輸出,通過我們上面對(duì)半加器真值表的觀察可知,輸出的相加信號(hào)可視為兩輸入信號(hào)的異或運(yùn)算,輸出的進(jìn)位信號(hào)可視為兩輸入信號(hào)的與運(yùn)算,因此實(shí)現(xiàn)如下:
// 半加器模塊
module half_adder (
input a,
input b,
output sum,
output carry
);
// 異或運(yùn)算完成相加
assign sum = a ^ b;
// 與運(yùn)算完成進(jìn)位
assign carry = a & b;
endmodule
- 接下來就是根據(jù)設(shè)計(jì)思路通過對(duì)半加器模塊和或門模塊的引用來完成一位全加器的頂層模塊設(shè)計(jì):
- 其基本語法仍然是賦值語句,只不過多了運(yùn)算符的操作,與C語言類似,Verilog也有多種運(yùn)算符(默認(rèn)具有C語言基礎(chǔ),僅對(duì)與C語言不同的進(jìn)行解釋):
- 算數(shù)運(yùn)算符:
-
+
:加法運(yùn)算符,或正值運(yùn)算符 -
-
:減法運(yùn)算符,或負(fù)值運(yùn)算符 -
*
:乘法運(yùn)算符 -
/
:除法運(yùn)算符 -
%
:模運(yùn)算符
-
- 位運(yùn)算符:
-
~
:取反 -
&
:按位與 -
|
:按位或 -
^
:按位異或 -
^~
:按位同或
-
- 邏輯運(yùn)算符:
-
&&
:邏輯與 -
||
:邏輯或 -
!
:邏輯非
-
- 關(guān)系運(yùn)算符:
-
<
:小于 -
>
:大于 -
<=
:小于等于 -
>=
:大于等于
-
- 等式運(yùn)算符:
-
==
:等于 -
!=
:不等于 -
===
:等于,與上面的等于不同的是,這里的等于對(duì)兩個(gè)操作數(shù)的要求是嚴(yán)格的,即對(duì)不定值x
和高阻值z
也會(huì)進(jìn)行比較,只有兩端嚴(yán)格相等時(shí)其結(jié)果才為1
,因此其結(jié)果不會(huì)出現(xiàn)不定值x
和高阻值z
,下面的不等于類似。 -
!==
:不等于
-
- 移位運(yùn)算符:
-
<<
:左移運(yùn)算符 -
>>
:右移運(yùn)算符
-
- 位拼接運(yùn)算符:
-
{}
使用大括號(hào)可以將兩個(gè)或多個(gè)信號(hào)的某些位拼接起來進(jìn)行運(yùn)算操作,其使用方法如下:{信號(hào)1的某幾位,信號(hào)2的某幾位,...,信號(hào)n的某幾位}
- 如果我們需要將
a
信號(hào)和b
信號(hào)進(jìn)行拼接,可用{a,b}
來進(jìn)行操作 - 除此之外,還可以通過重復(fù)發(fā)來進(jìn)行簡(jiǎn)化操作,如
{4{w}}
其相當(dāng)于{w,w,w,w}
- 可以通過嵌套的方式來表達(dá),如
{b,{3{a,b}}}
其相當(dāng)于{b,a,b,a,b,a,b}
-
- 縮減運(yùn)算符:縮減運(yùn)算符是單目運(yùn)算符,其也有與、或、非運(yùn)算,但是與位運(yùn)算不同的是,縮減運(yùn)算符是對(duì)單個(gè)操作數(shù)進(jìn)行或、與、非運(yùn)算,最后的結(jié)果為1位的二進(jìn)制數(shù),如與運(yùn)算:
- 算數(shù)運(yùn)算符:
// 全加器模塊
module full_adder (
input a,
input b,
input cin,
output sum,
output cout
);
// 對(duì)中間的變量進(jìn)行定義
wire s1, c1, c2;
// 對(duì)半加器的實(shí)例化
half_adder half_adder1(a, b, s1, c1);
half_adder half_adder2(s1, cin, sum, c2);
// 對(duì)或門的實(shí)例化
or_gate or_gate1(c1, c2, cout);
endmodule
reg [3:0] B;
reg C;
// 對(duì)B進(jìn)行縮減與運(yùn)算,其運(yùn)算過程相當(dāng)于 C = ((B[0] & B[1]) & B[2]) & B[3]
C = &B;
- 除此之外我們還需要對(duì)Verilog的數(shù)據(jù)類型有一定的了解:
- 常量
- 數(shù)字:
- 整數(shù):
- 二進(jìn)制整數(shù):
b或B
- 八進(jìn)制整數(shù):
o或O
- 十進(jìn)制整數(shù):
d或D
- 十六進(jìn)制整數(shù):
h或H
- 其表示方法有三種:
- <位寬><進(jìn)制><數(shù)字>,如
2'b00
就是一個(gè)兩位的二進(jìn)制數(shù)字00
- <進(jìn)制><數(shù)字>,在這種情況下,數(shù)字的位寬采用默認(rèn)位寬(由機(jī)器系統(tǒng)決定),最少為32位,因此不推薦
- <數(shù)字>,在這種描述方式中,采用默認(rèn)進(jìn)制(十進(jìn)制),但是描述不清,一般仍采用第一種表示方法
- <位寬><進(jìn)制><數(shù)字>,如
- 二進(jìn)制整數(shù):
-
x
和z
值:數(shù)電中,x
代表不定值,z
值代表高阻值。如:-
4'b10x0
表示4位的二進(jìn)制數(shù)從地位數(shù)起的第二位為不定值 -
12'dz或12'd?
表示12為十進(jìn)制數(shù),其值為高阻值 -
8'h4x
表示8位十六進(jìn)制,其低4位為不定值
-
- 負(fù)數(shù):在位寬表達(dá)式前加上一個(gè)減號(hào),如:
-8'd5
- 下劃線:可以具體的數(shù)字之間分隔開來以提高程序可讀性,如:
16'b1010_0000_1111_0101
- 整數(shù):
- 參數(shù):即用
parameter
定義一個(gè)標(biāo)識(shí)符代表一個(gè)常量,以提高程序可讀性和可維護(hù)性,常用于定義延遲時(shí)間和變量寬度。如:parameter msb = 7;
定義參數(shù)msb
為常量7。
- 數(shù)字:
- 變量:
-
wire
型:常用來表示用以assign
關(guān)鍵字指定的組合邏輯信號(hào),Verilog模塊中的輸入和輸出信號(hào)默認(rèn)自動(dòng)定義為wire
型??捎米鋈魏畏匠淌降妮斎?,也可以用做assign
語句或?qū)嵗妮敵?。其定義格式如下:wire [n-1:0] 數(shù)據(jù)名1,數(shù)據(jù)名2,...,數(shù)據(jù)名i
,其表示共有i條總線,每條總線內(nèi)有n條線路 -
reg
型:即寄存器數(shù)據(jù)類型,通過賦值語句可改變寄存器存儲(chǔ)的值,其初始值為不定值x,因此在使用reg
型時(shí)一般需要賦初值,否則在仿真中會(huì)出現(xiàn)不定值x,無法通過仿真確認(rèn)其結(jié)果。同時(shí)reg
型只表示被定義的信號(hào)將用在always
模塊,在always
模塊內(nèi)被賦值的每一個(gè)信號(hào)都必須被定義成reg
型。其定義格式如下:reg [n-1:0] 數(shù)據(jù)名1,數(shù)據(jù)名2,...,數(shù)據(jù)名i
。 -
memory
型:通過對(duì)reg
型變量建立數(shù)組來對(duì)存儲(chǔ)器建模,可以描述RAM型存儲(chǔ)器、ROM存儲(chǔ)器和reg文件。數(shù)組中的每一個(gè)單元通過一個(gè)數(shù)組索引進(jìn)行尋址。其定義格式如下:reg [n-1:0] 存儲(chǔ)器名[m-1:0];
-
- 常量
4.仿真文件
在生成bit文件下載到板子之前,我們一般會(huì)通過編寫仿真文件對(duì)已完成的模塊進(jìn)行驗(yàn)證,其仿真文件如下:
module full_adder_test_top;
// 定義輸入到全加器的信號(hào)
reg a_test, b_test, cin_test;
// 定義從全加器輸出的信號(hào)
wire sum_test, cout_test;
// 將輸入信號(hào)和輸出信號(hào)與全加器相連接
full_adder full_adder_test(
.a(a_test),
.b(b_test),
.cin(cin_test),
.sum(sum_test),
.cout(cout_test)
);
// 開始仿真
initial begin
// 初識(shí)時(shí)刻為0,對(duì)各個(gè)輸入信號(hào)進(jìn)行初始化
a_test = 0;
b_test = 0;
cin_test = 0;
// 過了20ns后改變信號(hào)值
#20 a_test = 1;
b_test = 0;
cin_test = 0;
#20 a_test = 1;
b_test = 1;
cin_test = 0;
#20 a_test = 1;
b_test = 1;
cin_test = 1;
#20 a_test = 0;
b_test = 0;
cin_test = 1;
// 調(diào)用系統(tǒng)函數(shù)使仿真停止
#20 $stop;
end
endmodule
從這里我們看到一般仿真文件的編寫規(guī)則:首先,仿真模塊無輸入無輸出;第二,定義仿真的全加器輸入信號(hào)一般使用reg
型,輸出一般使用wire
型;第三,使用initial
塊確定仿真過程中的各種輸入值的初始化以及賦值;第四,其基本單位為ns。文章來源地址http://www.zghlxwxcb.cn/news/detail-713818.html
到了這里,關(guān)于Verilog學(xué)習(xí)二:設(shè)計(jì)一個(gè)一位全加器的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!