# 數位電路實驗暨 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)