準(zhǔn)備
作為一個(gè)軟件工程師,計(jì)算機(jī)是我們賴以生存的工具,所有我們開發(fā)的軟件都在計(jì)算機(jī)上執(zhí)行,那么通過(guò)與或非等最基礎(chǔ)的門電路構(gòu)建一個(gè)計(jì)算機(jī)一方面可以更深入的了解計(jì)算機(jī),一方面也可以更好的開發(fā)軟件。
基于門電路從頭開始當(dāng)然可以,但連接電路比較費(fèi)勁,所以這里采用了 verilog + Fpga 的形式。verilog + Fpga 本質(zhì)就是自己設(shè)計(jì)連接電路,和自己從頭基于門電路連接一樣。
第一個(gè)項(xiàng)目來(lái)搭建開發(fā)環(huán)境和構(gòu)建最基礎(chǔ)的幾個(gè)電路。
硬件
- tangnano 4k
硬件采用國(guó)產(chǎn)的 tangnano 而沒(méi)有采用國(guó)外大廠的。一方面為了支持國(guó)產(chǎn) IC 一方面中國(guó)是以制造業(yè)為主的國(guó)家、國(guó)產(chǎn)的 Fpga 性價(jià)比較高。
搭建環(huán)境
常見的 EDA 工具一般僅支持 windows 和 linux 不支持 macos,這里不對(duì)專有和開源軟件的優(yōu)劣做對(duì)比,以免引戰(zhàn)。在本教程中我盡量使用開源軟件而不使用廠家的專有軟件。
# 安裝綜合的工具
brew install yosys
pip install apycula
brew install eigen
# 安裝 gowin 的布線工具
git clone git@github.com:YosysHQ/nextpnr.git
cmake . -DARCH=gowin
make -j$(nproc)
sudo make install
# 參考 https://github.com/YosysHQ/nextpnr#nextpnr-gowin
# 安裝刷入 Fpga 的工具
brew install openfpgaloader --HEAD
# 安裝編譯成可仿真的文件
brew install icarus-verilog
# 安裝查看波形的軟件
brew install --cask gtkwave
綜合和布線其實(shí)就是生成電路,和物理的連接電路一樣,具體的 Fpga 的原理請(qǐng)參考 https://zh.wikipedia.org/zh-tw/%E7%8E%B0%E5%9C%BA%E5%8F%AF%E7%BC%96%E7%A8%8B%E9%80%BB%E8%BE%91%E9%97%A8%E9%98%B5%E5%88%97 。
Nand
Nand 又稱為與非門,其他門一般通過(guò)它來(lái)構(gòu)建,是基本門電路,原因如下:
- 考慮電子元件的成本,也許從邏輯上來(lái)看與非門和或非門比與門和非門更復(fù)雜,但是實(shí)際上由于Mos管的物理結(jié)構(gòu),實(shí)現(xiàn)與非門和或非門需要的元件其實(shí)更少,成本更低,而簡(jiǎn)單的與門和或門其實(shí)在結(jié)構(gòu)上比前者更復(fù)雜。
- 或非門和與非門具有邏輯完備性,任何一個(gè)門通過(guò)組合可以實(shí)現(xiàn)任意電路,而與門和或門不具有這樣的能力
- 仍然和電子元件的物理結(jié)構(gòu)有關(guān),與非門和或非門實(shí)際運(yùn)行效率比與門和或門更高。
與非門的真值表:
| A | B | A NAND B |
|---|---|---|
| 0 | 0 | 1 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
非
非門是用 Nand(與非門)實(shí)現(xiàn)的,代碼如下:
/**
* Not gate:
* out = not in
*/
`include "Nand.v"
`default_nettype none
module Not(
input in,
output out
);
Nand NAND(.a(in), .b(1'b1), .out(out));
endmodule
非門顧名思義就是取反操作,0 轉(zhuǎn)成 1 , 1 轉(zhuǎn)成 0 。所以只要輸入和 1 與然后取反就可以了,利用與非門很容易做到。
或
或門利用非門和與非門實(shí)現(xiàn),a or b == not (not a and not b),如果想了解這個(gè)公式怎么來(lái)的,請(qǐng)查閱“德摩根定律”。
/**
* Or gate:
* out = 1 if (a == 1 or b == 1)
* 0 otherwise
*/
`default_nettype none
module Or(
input a,
input b,
output out
);
wire nota;
wire notb;
Not NOT1(.in(a), .out(nota));
Not NOT2(.in(b), .out(notb));
Nand NAND(.a(nota), .b(notb), .out(out));
endmodule
與
與門很簡(jiǎn)單,與非門的結(jié)果取反就行了。
/**
* And gate:
* out = 1 if (a == 1 and b == 1)
* 0 otherwise
*/
`default_nettype none
module And(
input a,
input b,
output out
);
wire notaandb;
// your implementation comes here:
Nand NAND(.a(a), .b(b), .out(notaandb));
Not NOT(.in(notaandb), .out(out));
endmodule
異或
異或門是第一個(gè)有挑戰(zhàn)的門電路,異或門的意義是兩個(gè)輸入不同結(jié)果為 1 ,w1 和 w2 分別斷言 a = 1, b = 0 和 a = 0 , b = 1 ,w1 or w2 是上述有一個(gè)成立結(jié)果就為 1。因?yàn)?a b 只有兩種取指,上述描述就覆蓋了所有情況。
/**
* Xor (exclusive or) gate:
* If a<>b out=1 else out=0.
*/
`include "Not.v"
`include "And.v"
`include "Or.v"
`default_nettype none
module Xor(
input wire a,
input wire b,
output wire out
);
wire nota; //new wire must be declared
wire notb;
Not NOT1(.in(a), .out(nota)); //NOT1 is instance name
Not NOT2(.in(b), .out(notb));
wire w1;
wire w2;
And AND1(.a(a),.b(notb),.out(w1));
And AND2(.a(nota),.b(b),.out(w2));
Or OR(.a(w1),.b(w2),.out(out));
endmodule
真值表如下:
| A | B | A XOR B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
激勵(lì)
所謂激勵(lì)和軟件工程師的單元測(cè)試差不多,以下激勵(lì)就是用來(lái)測(cè)試異或門的,很好理解。
a = ... b= ... 就是給 a b 賦值來(lái)測(cè)試在不同取值下的結(jié)果。結(jié)果通過(guò) display 寫入Xor.out 文件中,波形寫入 Xor_tb.vcd 中,待會(huì)介紹波形。
`include "Xor.v"
`default_nettype none
module Xor_tb();
integer file;
reg a = 0;
reg b = 0;
wire out;
Xor XOR(
.a(a),
.b(b),
.out(out)
);
task display;
#1 $fwrite(file, "| %1b | %1b | %1b |\n", a,b,out);
endtask
initial begin
$dumpfile("Xor_tb.vcd");
$dumpvars(0, Xor_tb);
file = $fopen("Xor.out","w");
$fwrite(file, "| a | b |out|\n");
a=0;b=0;
display();
a=0;b=1;
display();
a=1;b=0;
display();
a=1;b=1;
display();
$finish();
end
endmodule
編譯
編譯 Xor_tb.v,有編譯錯(cuò)誤在這步就會(huì)打印出來(lái)。
iverilog -o Xor_tb.vvp Xor_tb.v
仿真(模擬)
所謂仿真其實(shí)就是模擬,仿真其實(shí)就是模擬硬件,在不同的輸入(信號(hào))下展現(xiàn)不同的波形。
vvp sample_tb.vvp
open -a gtkwave
使用 gtkwave 查看波形,右鍵點(diǎn)擊Xor_tb ,選擇 a b out ,然后 append ,刪除內(nèi)部變量??梢钥吹疆?dāng) a b 不同結(jié)果為 1 (高電平)。
[圖片上傳失敗...(image-2cac4e-1654746740561)]
比較
*.cmp 是提供的比較文件, *.out 是我們執(zhí)行仿真產(chǎn)生的結(jié)果文件。二者應(yīng)該是相同的,采用這種方式來(lái)斷言我們的程序?qū)懙膶?duì)不對(duì)。
diff Xor.out Xor.cmp
如果程序?qū)懙膶?duì)什么也不輸出,否則輸出不同的地方。
上傳到 tangnano
綜合和布線
綜合和布線就是通過(guò) verilog 產(chǎn)生電路的過(guò)程。
yosys -p "read_verilog Xor.v; synth_gowin -json Xor.json"
綜合比較簡(jiǎn)單,布線需要引入所謂的約束文件:
IO_LOC "out" 10;
IO_LOC "a" 15;
IO_LOC "b" 14;
14 和 15 是 tangnano 的兩個(gè)按鈕,分別被綁定到了 a b ,10 是 led 燈。需要注意的是按鈕松開的時(shí)候是 1 ,按下是 0 。約束文件把程序中的變量綁定到了實(shí)際的物理硬件,改變物理量就改變了變量。
nextpnr-gowin --json Xor.json --write pnrXor.json --device GW1NSR-LV4CQN48PC6/I5 --cst tangnano4k.cst
最后生成二進(jìn)制文件。
gowin_pack -d GW1NSR-LV4CQN48PC6/I5 -o pack.fs pnrXor.json
寫入硬件
刷入 Fpga 。
openFPGALoader -b tangnano4k pack.fs
測(cè)試
實(shí)際按下按鈕試試。當(dāng)按鈕的狀態(tài)不同時(shí),結(jié)果為 1 ,反之結(jié)果為 0。完成了異或門。下面是視頻,點(diǎn)擊即可。