# 數位電路實驗暨 verilog 學習(一)Introduction
本文為專題實作與自我練習紀錄筆記,望能幫助verilog入門
內容皆參考自以下網站
[南京大學數位電路實驗](https://nju-projectn.github.io/dlco-lecture-note/exp/01.html)
[YSYX 一生一芯網站](https://ysyx.oscc.cc/docs/2205/)
# 操作教學
前置作業參考YSYX網站
1. 創建一個.v
EX : Switch.v (名稱不用跟module一樣)
如果希望內部將各部份電路分隔為數個module進行實作,有以下方法 :
1.個別創建 2. 直接在同一個檔案宣告
```
1. 創建檔案
vim (filename).v
2. 將各個module分別撰寫在個別的.v檔中
module XorGate(
input wire a,
input wire b,
output wire y
);
assign y = a ^ b;
endmodule
3. 在最後的.v檔中的呼叫module
module TopModule(
input wire a,
input wire b,
output wire xor_out,
output wire and_out
);
// XorGate
XorGate u1 (
.a(a),
.b(b),
.y(xor_out)
);
// AndGate
AndGate u2 (
.a(a),
.b(b),
.y(and_out)
);
endmodule
4. 或同一檔案...就把他們放在一起
``
```
```
2. 撰寫TestBench,使用.cpp檔以C++撰寫
* 1. vim
```
vim (Test bench file name) + .cpp
```
```
#include "VTopModule.h"
#include "verilated.h"
#include "verilated_vcd_c.h"
int main(int argc, char **argv, char **env) {
Verilated::commandArgs(argc, argv);
VTopModule* top = new VTopModule;
// 初始化變量
Verilated::traceEverOn(true);
VerilatedVcdC* tfp = new VerilatedVcdC;
top->trace(tfp, 99);
tfp->open("waveform.vcd");
// input signal
top->a = 0;
top->b = 0;
// 30個cycle
for (int i = 0; i < 40; i++) {
if (i == 10) top->a = 0, top->b = 1;
if (i == 20) top->a = 1, top->b = 0;
if (i == 30) top->a = 1, top->b = 1;
top->eval();
tfp->dump(i);
}
tfp->close();
delete top;
return 0;
}
```
3. 再來生成模擬檔案
* 指令 (1):
```
verilator --cc --trace (希望模擬的module) --exe (Test bench 的.cpp檔)
```
* 指令 (2):
```
make -j -C obj_dir/ -f V +(.v檔的名字)+ .mk V +(.v檔的名字)
```
4. 運行檔案
* 指令 (3):
```
./obj_dir/V +(.v檔的名字)
```
5. 查看波形
```
gtkwave waveform.vcd
```
# 南京大學數位電路實驗一:選擇器
* 2 to 1 mux
```
module m_mux21(a,b,s,y);
input a;
input b;
// chosing signal
input s;
// final output
output y;
// assign 為賦予用function 像是把個logic gate 的結果接到某條output或logic gate
assign y = (~s&a)|(s&b);
// ~ : not , & : and gate , | : or gate
endmodule
//module最後必打
```
> 由於verilator 內有辦法直接使用if 等function ,建議能用卡諾圖,簡化logic gate 的設計架構
```
*****************sim.cpp*****************
#include "verilated.h"
#include "verilated_vcd_c.h"
#include "obj_dir/Vmux21.h"
VerilatedContext* contextp = NULL;
VerilatedVcdC* tfp = NULL;
static Vmux21* top;
void step_and_dump_wave(){
top->eval();
contextp->timeInc(1);
tfp->dump(contextp->time());
}
void sim_init(){
contextp = new VerilatedContext;
tfp = new VerilatedVcdC;
top = new Vmux21;
contextp->traceEverOn(true);
top->trace(tfp, 0);
tfp->open("dump.vcd");
}
void sim_exit(){
step_and_dump_wave();
tfp->close();
}
int main() {
sim_init();
top->s=0; top->a=0; top->b=0; step_and_dump_wave(); // 将s,a和b均初始化为“0”
top->b=1; step_and_dump_wave(); // 将b改为“1”,s和a的值不变,继续保持“0”,
top->a=1; top->b=0; step_and_dump_wave(); // 将a,b分别改为“1”和“0”,s的值不变,
top->b=1; step_and_dump_wave(); // 将b改为“1”,s和a的值不变,维持10个时间单位
top->s=1; top->a=0; top->b=0; step_and_dump_wave(); // 将s,a,b分别变为“1,0,0”,维持10个时间单位
top->b=1; step_and_dump_wave();
top->a=1; top->b=0; step_and_dump_wave();
top->b=1; step_and_dump_wave();
sim_exit();
}
```
> 每個logic circuit 中都需要一個模擬的C/C++程式碼進行模擬,以上為實驗網站提供,sim.cpp大多需要自己寫!!!所以要注意該格式為何(後續會專門提到)
> 但需要注意的是,如果只用if等lib中的function其實就跟在寫一般的C差不多,所以需要盡量使用基本的and,or等logic gate 進行實做
# 結構化module
> 此方式是用類似logic circuit的方式進行電路實現,因此需要用到大量的logic circuit design的基礎
```
// And gate module
module my_and(a,b,c);
input a,b;
output c;
assign c = a & b;
endmodule
// OR gate module
module my_or(a,b,c);
input a,b;
output c;
assign c = a | b;
endmodule
// Not gate module
module my_not(a,b);
input a;
output b;
assign b = ~a;
endmodule
//最終整合到頂部module中
module mux21b(a,b,s,y);
input a,b,s;
output y;
wire l, r, s_n; // 宣告內部電路電線,注意語法!
my_not i1(.a(s), .b(s_n));
my_and i2(.a(s_n), .b(a), .c(l));
my_and i3(.a(s), .b(b), .c(r));
my_or i4(.a(l), .b(r), .c(y));
endmodule
```
> 這裡要注意的是,跟C的語法不同,module的輸入參數設置模式與C中直接 my_not (s,s_n)是不同的,verilator中需要在module被call的過程中詳細寫出頂層module中哪條電路對應到該子module中的哪條input/output
> 範例如下 :
```
module my_not(a,b); //其中有兩參數a,b分別代表input/output
```
> 使用時,欲將頂層module的電路連入此module中,則寫作以下
```
module my_not(.a(頂層module的電線),.b(頂層module的電線));
```
> 我的理解是,input/output為宣告一個port,但接入的signal線路則需要另行宣告,並在call的過程中以
```
.(input port name)(circuit name)
```
> 的方式寫出
# 行為module
> 據我的理解是像寫C一樣,使用if等function直接描述電路應該要做到的功能,此方式就不涉及logic gate等方面的知識背景
```
module mux21c(a,b,s,y);
input a,b,s;
output reg y;
// y在always块中被赋值,一定要声明为reg型的变量
always @ (*)
if(s==0)
y = a;
else
y = b;
endmodule
```
> 這裡要注意的是,always/if-else的運用
```
alway @ (event) begin
(statement)
end
```
> event為觸發事件,簡而言之,當event發生變化時時,begin下述行為直到end語句
以下為範例
```
always@ (a,b,s) begin
out = a*b*s;
end
```
這種情況下 a、b、s 三條訊號線只要有其中一條變化,則執行begin內部指令
但這有問題的點在於,內部的線程可能多到沒辦法列出,因此大多會改為以下方式
```
always@ (*) begin
out = a*b*s;
end
```
> 此範例即為利用' * '代替所有線程
> 接下來將介紹if-else語法
```
//傳統C語法
if(s==0)
y = a; x = b;
else
y = b; x = a;
//verilog語法
if(s==0)
begin
y = a;
x = b;
end
else
begin
y = b;
x = a;
end
```
> 組合各線路進行運算 , 使用assign
```
assign y = a*b;
```
> 需要注意的是,變數宣告的形式有wire/reg兩種,差別在於在assign 使用 wire, 在always內的變量則只能宣告為reg
>
> 另一點要注意的是,對於一個module來 說,input/output/inout都被默認為wire型態,但如果該變量(如上一個範例中的y變量)在always中被'賦值'的話,就必須額外宣告reg
>
> 再著,大多數event內都會使用clock或reset的變量,如以下
```
input rst;
input clk;
```
> 而我們可以使用verilog自身定義的posedge/negedge來描述此訊號是要在上升/下降時進行運算,如以下
```
always@ (posedge clk or posedge rst) begin
```
> 則代表若clk(0->1)or rst (0->1)則開始運算
# Case & Signal bits width
> 先由case語法開始,此類語法與if-else相似,只是在實例化電路時所給出的結構會不同,使用方式如下
```
case (s)
0: y = a[0];
1: y = a[1];
2: y = a[2];
3: y = a[3];
default: y = 1'b0;
endcase
```
> 在其中,可以得知輸入的判斷signal為s變量,當s分別等於0,1,2,3時則會相對應的將a[s]賦值給y
>另一例子如下
```
case (s)
2'b00 : y = a[0];
2'b01 : y = a[1];
2'b10 : y = a[2];
2'b11 : y = a[3];
default: y = 2'b00;
endcase
```
> 在其中我們可以發現一個表達方式,即2'b00這種。
> 此表達式所表達的意思為以下
```
(bits width)'(進位模式)+(在寬度中各個bits的狀態)
```
> 舉例而言
```
10 ' b 1000010000 = 一個10位元寬度且為2進位信號
```
> 當然,中央的英文字母是可以更換的,各自代表如下
```
b = 二進位 / d = 10進位 / h = 16進位 / o = 8進位
```
# 最終實驗驗收 : 實現二位四選一選擇器
Module code
```
module mux41(a,s,y);
input [3:0] a;
input [1:0] s;
output reg y;
// output should using reg
always@(*) begin
case (s)
2'b00 : y = a[0];
2'b01 : y = a[1];
2'b10 : y = a[2];
2'b11 : y = a[3];
default : y = 1'b0;
endcase
end
endmodule
```
Simulation code
```
#include "verilated.h"
#include "verilated_vcd_c.h"
#include "obj_dir/Vmux41.h"
VerilatedContext* contextp = NULL;
VerilatedVcdC* tfp = NULL;
static Vmux41* top;
void step_and_dump_wave(){
top->eval();
// eval函數調用則代表更新各訊號狀態
contextp->timeInc(1);
// 增加模擬時間,數字代表時間有多少個單位時間長
tfp->dump(contextp->time());
// 把波形丟到vcd檔
}
void sim_init(){
contextp = new VerilatedContext;
tfp = new VerilatedVcdC;
top = new Vmux41;
contextp->traceEverOn(true);
//啟動追蹤
top->trace(tfp, 0);
tfp->open("dump.vcd");
}
void sim_exit(){
step_and_dump_wave();
tfp->close();
}
int main() {
sim_init();
// c++中 二進制表示法為0bxxxx
top->s=0b00; top->a=0b1110; step_and_dump_wave();
top->a=0b0001; step_and_dump_wave();
top->s=0b01; top->a=0b1110; step_and_dump_wave();
top->a=0b0010; step_and_dump_wave();
top->s=0b10; top->a=0b1010; step_and_dump_wave();
top->a=0b0100; step_and_dump_wave();
top->s=0b11; top->a=0b0111; step_and_dump_wave();
top->a=0b1001; step_and_dump_wave();
sim_exit();
}
```
Terminal instruction
```
verilator --cc --trace mux41.v --exe sim.cpp
```
```
make -j -C obj_dir/ -f Vmux41.mk Vmux41
```
```
./obj_dir/Vmux41
```
```
gtkwave dump.vcd
```
第一段落到此結束
下一回 : [實驗二](https://hackmd.io/@1p0_VOUHTXGcHlBU9A0OEA/B1UjuRn9A)