如何在FPGA上做一個沒啥用的mcu
簡介
近段時間做專案,涉及到一些感測器資料的採集,比如溫度感測器DHT11
,這種東西使用FPGA來做,為了實現他的時序,如果自己寫的話那是真的不容易,但是對於專案來說,這個東西有需要做,怎麼辦?於是在FPGA或者CPLD中做一個佔用資源可控,且能在各個平臺下移植的可程式設計狀態機就進入了我的視野。
說時遲那時快,花了一天時間寫了一個簡單的8位mcu,在功能上僅僅只有簡單的輸入輸出功能,加減法,邏輯運算,支援跳轉,呼叫,比較等指令。
主要實現功能
目前該mcu的主要情況如下:
- 內部16個暫存器,s0 ~ sF
- 設計一個深度為31的程式指標棧,用於支援CALL命令
- 支援8位加減法運算,通過設計的標誌位可以實現16為,32位的加減法運算
- 支援邏輯運算,AND,OR,XOR
從實現功能上來看,似乎比較少,但是我們依舊可以使用這些有限的命令實現我們的初衷,就是實現與一些簡單感測器的互動,甚至可以實現I2C,SPI的通訊介面。
在指令上為了不用自己開發將彙編翻譯為機器指令的工具,這裡直接和xilinx的picoblaze的指令保持一致,換句話說,使用xilinx的工具可以將我們的彙編程式碼翻譯成支援這個mini-mcu的機器指令。
有什麼特有的特性
在一些資源有限的CPLD上,完全實現一個mcu是不現實,為了契合我的初衷,並且不讓資源被浪費太多,我位元組寫了指令碼,可以根據寫的彙編程式碼,將彙編程式碼中使用到的命令提取出來,並會直接將mini-mcu
與這些命令有關的部分保留,裁剪掉其他沒有用到的部分,但是輸入輸出部分將會一直保留。另外由於沒有實現其他一些複雜的指令,這些指令都是在兩個時鐘週期中完成的,也就是說基於這個特性,可以實現精確的時鐘延時。
環境準備
由於自己適應了linux的環境,所以實現的指令碼都是使用bash
寫的,包括裁剪mcu,如果不會linux指令也沒事,只不過不能自動化的生成這些東西,需要自己手動來,包括裁剪mcu,當然這種情況下,你可以不裁剪。
說一說在為了支援linux指令,在windows系統上應該怎麼準備環境
step1:安裝cygwin以支援bash指令碼
Cygwin就是一個windows軟體,該軟體就是在windows上模擬linux作業系統 ,簡言之,cygwin是一個在windows平臺上執行的 linux模擬環境,使用一個Dll(動態連結庫)來實現 這樣,我們可以開發出Cygwin下的UNIX工具,使用這個DLL執行在Windows下。
安裝方法
1、下載cygwin
安裝器
下載地址:官方地址
然後就可以使用這個安裝器進行安裝了
2、啟動安裝器進行安裝
安裝器有三種安裝模式可供選擇:
①Install from Internet,這種模式直接從Internet安裝,適合網速較快的情況;
②Download Without Installing,這種模式只從網上下載Cygwin的元件包,但不安裝;
③Install from Local Directory,這種模式與上面第二種模式對應,當你的Cygwin元件包已經下載到本地,則可以使用此模式從本地安裝Cygwin
說明:當你安裝過,在執行該安裝程式可以選擇本地安裝,然後新增需要擴充套件的命令。
第一次安裝使用第一種方式進行安裝:
在下載的同時,建議將Cygwin安裝元件也儲存到了本地,以便以後能夠再次安裝,這一步選擇安裝過程中從網上下載的Cygwin元件包的儲存位
選擇連線方式
這一步選擇連線的方式,選擇你的連線方式,然後點選下一步,會出現選擇下載站點的對話方塊,如下圖所示
①Use System Proxy Settings 使用系統的代理設定
②Direct Connection 一般多數使用者都是這種直接連線的網路,所以都是直接使用預設設定即可
③Use HTTP/FTP Proxy 使用HTTP或FTP型別的代理。如果有需要,自己選擇此項後,設定對應的代理地址和埠,即可
選擇下載站點
不同的映象存放了不同的包,為了獲得最快的下載速度,我們可以新增網易開源映象http://mirrors.163.com/cygwin/
或者 阿里雲映象http://mirrors.aliyun.com/cygwin/
開始自動搜尋
選擇需要下載安裝的元件包
這一步比較重要,為了之後更好的使用該軟體,建議自己在這裡的時候就選好需要使用的元件,或者說支援的命令。
最核心的,記住一定要安裝Devel這個部分的模組,其中包含了各種開發所用到的工具或模組。
下面推薦推幾個元件
- fish:一個shell,具有良好的互動提示,強烈建議安裝,後面的操作也和其相關
- lynx:命令安裝元件的必須工具,強烈推薦安裝此項,方便之後擴充套件命令
- 其他的自選,比如 gcc,curl,python,tclsh等。學習FPGA,建議安裝tclsh
元件可以在search框輸入後搜尋,然後選中元件,在new列雙擊,當看到版本號後,安裝就會將此元件安裝上。
確認並開始安裝
安裝好之後,將cygwin安裝路徑下的bin目錄新增到環境變數,方便使用
為了讓我們更舒服的使用,我們先把預設的shell設為fish,當然,若果沒安裝fish就算了
當我們沒配置fish shell,使用預設的shell時我們開啟cygwin
的終端是這樣的
在終端輸入以下命令後下次重啟就可以了。
echo "fish" >> /etc/profile
當然此時要直接切換到fish可以在終端直接輸入fish
,切換過來就是這樣的了:
step2:安裝verilog小巧的模擬工具-iverilog
下載連結:windows版本iverilog
下載後直接安裝,當然為了之後使用方便強烈建議安裝好將安裝路徑下的bin
目錄和安裝目錄下的gtkwave/bin
目錄加入環境變數。
step3:主要工具準備完畢,在隨意來個編輯器
編輯器在這裡推薦使用vscode
,後面的說明也都會基於這個編輯器。
下載連結:vscode官網
注意,記住你的安裝路徑,
我們開啟他,同樣為了方便使用,在這裡先對其進行簡單的配置:
首先安裝幾個必要的外掛
在這個裡面搜尋,為了支援中文,你可以搜尋chinese
,進行安裝,之後又就是中文顯示了。其他的外掛可以暫時不用安裝,之後遇到相應的檔案後,軟體會自動推薦你安裝,我安裝的外掛如下:
關鍵步驟
在搜尋框搜尋term
然後配置一下:
主要就是這幾個,大家最好把這幾項先配置好,省的之後一項一項配置。
{ "terminal.integrated.shell.windows": "D:\\cygwin64\\bin\\fish.exe", "files.autoSave": "onFocusChange", "files.autoGuessEncoding": true, "editor.mouseWheelZoom": true }
下載mini-mcu
下載地址:mini-mcu
如果你安裝cygwin時也安裝了git,那麼在cygwin的終端中可以使用:
git clone https://gitee.com/yuan_hp/mini-mcu.git
直接克隆。
然後我們使用vscode
開啟我們mini-mcu
的資料夾,並在開啟vscode的終端。
為了感受一下之後開發的方便,在終端中輸入以下命令:
該命令列會直接編譯專案中software
一級目錄下的.psm
檔案,也就是我們的彙編程式碼檔案,並生成對用的rom.v
檔案,同時裁剪mini-mcu
,命令./run
將會呼叫iverilog
模擬專案並用gtkwave
代開模擬的波形圖
**特別注意:**當你想開發新的功能時,你可以先不關閉gtkwave,修改software
下的程式碼後,執行以下命令
重新整理並行檔案的資料,然後在gtkwave重新載入資料:
專案檔案結構
├── head.v
用於裁剪mini-mcu的巨集檔案
├── images
存放著圖片
├── mcu.v
mini-mcu原始碼
├── README.md
├── rom.v
編譯彙編自動成成的程式儲存器
├── run
專案控制指令碼
├── run.sh
├── sim
生成的模擬檔案
│ ├── wave
│ └── wave.lxt2
├── software
編寫的彙編程式碼
│ ├── test.psm
指令碼會編譯的程式碼
│ ├── 第一個例子
│ │ └── start.psm
│ ├── 簡單按鍵檢測
│ │ └── keycheck.psm
│ ├── 流水燈程式
│ │ └── led_water.psm
│ └── 數碼管計數
│ └── seg_counter.psm
├── step_fpga
小腳丫fpga的歷程專案,執行 ./run -g 會將檔案拷貝到這個目錄下
├── tb.v
模擬testbech檔案
├── tmp
執行指令碼時生成的臨時資料夾
│ ├── kcpsm6.exe
│ ├── KCPSM6_session_log.txt
│ ├── ROM_form.v
│ ├── test.fmt
│ ├── test.hex
│ ├── test.log
│ ├── test.psm
│ └── test.v
├── tools
模擬一些工具和指令碼
│ ├── bin
│ │ ├── compile
│ │ ├── hex2rom
│ │ └── msim
│ └── kcpsm
│ ├── kcpsm6.exe
│ └── ROM_form.v
├── upCloud
└── window.v 專門用來檢視mcu內部變數的模組
已經支援的指令
- LOAD
- JUMP
- JUMP C
- JUMP NC
- JUMP Z
- JUMP NZ
- CALL C
- CALL NC
- CALL Z
- CALL NZ
- CALL
- RETURN
- RETURN C
- RETURN NC
- RETURN Z
- RETURN NZ
- AND
- OR
- XOR
- INPUT
- OUTPUT
- ADD
- ADDCY
- SUB
- SYBCY
- COMPARE
- TEST
- SL0
- SL1
- RL
- RR
- SR0
- SR1
- SRA
- LRA
開發你的專案
step1:編寫程式碼
指令碼只會自動搜尋software
一級目錄下的.psm
檔案!
start: LOAD sA , 23; 載入暫存器A的值為 0x23 ADD sA,02;寄存區A的值加上 0x02
step2:編譯
執行命令
./run -c
編譯檔案
step3:模擬verilog專案
執行命令:
./run
step4:板上驗證
拷貝專案目錄下的mcu.v, rom.v , head.v
到實際的FPGA實際專案的目錄下,進行,並編寫專案的頂層檔案:參考如下:
module top ( input clk_in, //輸入系統12MHz時鐘 //4bit撥碼開關輸入 input [3:0] sw, input [3:0] key, //按鍵輸入 //數碼管 output [8:0] seg_led_1, output [8:0] seg_led_2, //rgb output reg[2:0]rgb, //led output led1, output led2, output led3, output led4, output led5, output led6, output led7, output led8 ); wire clk ,clko,rst; reg [7:0] out; assign {led8,led7,led6,led5,led4,led3,led2,led1} = out; assign clk = clk_in; reg rst_n_in; //復位訊號 reg [17:0]cnt ; always @(posedge clk) begin if(cnt>=18'h3ffff)begin rst_n_in <= 1'b1; end else begin cnt <= cnt +1; rst_n_in <= 0; end end /* divide #( .N(1) ) u1 ( .clk(clko), .rst_n(rst_n_in), .clkout(clk) ); */ //----------- mini-mcu 相關------------ wire [11:0]address; wire [17:0] instruction; wire bram_enable, read_strobe, write_strobe; reg [7:0] in_port; wire [7:0] port_id, out_port; //----------- 數碼管 相關------------ reg[3:0] seg_data_1, seg_data_2; //輸出引腳 always @(posedge clk )begin if(write_strobe)begin case(port_id) 8'h00:{seg_data_1,seg_data_2} <= out_port;//bcd編碼的2個數碼管 8'h01:out <= out_port; //LED控制 8'h02:rgb <= out_port[2:0]; //rgb default:out <=out; endcase end else out <= out; end //輸入引腳 always@(*)begin if(read_strobe) begin case(port_id) 8'h00: in_port = {key[3:0],sw[3:0]}; //按鍵 4bit撥碼開關輸入 endcase end end /********************************** * 例化mini-mcu **********************************/ mcu mcu( .clk(clk), //系統時鐘 .rst_n( rst_n_in), //復位 0 --> 復位 .address( address), //程式取址地址 .instruction( instruction), //指令輸入 .bram_enable( bram_enable), //程式rom使能 1-->使能 .in_port( in_port), //輸入口 .read_strobe( read_strobe), //輸入口使能 .port_id( port_id), //io口地址 .out_port( out_port), //輸出口 .write_strobe( write_strobe) //輸出口寫使能 ); rom rom( .clk( clk), .address( address), //程式取址地址 .instruction( instruction), //指令輸入 .enable( bram_enable) //程式rom使能 1-->使能 ); /********************************** *數碼管顯示 是bcd碼 **********************************/ seg_display seg_display( .seg_data_1(seg_data_1), .seg_data_2(seg_data_2), .seg_led_1(seg_led_1), .seg_led_2(seg_led_2) ); endmodule
個人實驗開發板
我做實驗的開發板為小腳丫FPGA,型號為STEM-MX02-C
,這是U盤模式的,晶片為Lattice的,專案下已經有對應的工程,就是step_fpga
,如果你的開發板也是這個,同時也安了diamond,也將diamond的可執行路徑加入了環境變數,那麼可以執行命令./run -g
,就會編譯程式碼,拷貝檔案,綜合工程,下載到開發板了,你可能需要修改的是在step_fpga
下的run.tcl
指令碼的最後一行。pnmainc
是diamond工具的tcl命令工具!
幾個例項
流水燈:
;系統時鐘為倍頻到120MHz ;目標硬體為 小腳丫FPGA step-maxo2-c,這個型號是U盤模式,流檔案會下載到mcu,每次上電由mcu配置FPGA ;輸入 constant sw_port,00 ;定義按鍵四段撥碼開關 【按鍵 : 開關 】 ;輸出 constant seg_port,00 ;定義數碼管地址 constant led_port,01 ;定義led_port為常量01 constant rgb_port,02 ; rgb燈 start: load sA,FE ; led等控制 load sB,12 ; 初始化數碼管顯示 12 load sC,00000111'b ; ' rgb 滅 output sC,rgb_port ;rgb不量 input sD,sw_port ; 讀一次io口 output sB, seg_port ;數碼管顯示 loop: output sA, led_port ;流水燈實現 RL sA ;迴圈左移 call delay_500ms jump loop ;迴圈 delay_500ms: LOAD s2, 09 ; 500000us / (1/1.2us) --> 計數次數 LOAD s1, 27 LOAD s0, c0 jump software_delay software_delay: LOAD s0, s0 ;pad loop to make it 10 clock cycles (5 instructions), if clk 12MHz --> 1/1.2 us SUB s0, 01 SUBCY s1, 00 SUBCY s2, 00 JUMP NZ, software_delay RETURN
按鍵檢測:
;系統時鐘為12MHz ;目標硬體為 小腳丫FPGA step-maxo2-c,這個型號是U盤模式,流檔案會下載到mcu,每次上電由mcu配置FPGA ;實現按鍵檢測,按一下key1,led1將會翻轉一次,程式簡單,因此注意手不要抖,正常按法按 ;輸入 constant sw_port,00 ;定義按鍵四段撥碼開關 【按鍵 : 開關 】 ;輸出 constant seg_port,00 ;定義數碼管地址 constant led_port,01 ;定義led_port為常量01 constant rgb_port,02 ; rgb燈 start: load sA,FF ; led等控制 output sA,led_port load sB,00 ; 初始化數碼管顯示 load sC,FF ; ' rgb 滅 output sC,rgb_port ;rgb input sD,sw_port ; 讀一次io口 loop: input sF,sw_port AND sF,00010000'b ;' COMPARE sF,00 JUMP Z,keycheck jump loop ;迴圈 keycheck: CALL delay_200ms input sF,sw_port AND sF,00010000'b ;' COMPARE sF,00 CALL Z,led_sh JUMP loop led_sh: XOR sA,00010000'b ;' OUTPUT sA,led_port RETURN delay_200ms: LOAD s2, 03 ; LOAD s1, a9 LOAD s0, 80 jump software_delay delay_500ms: LOAD s2, 09 ; 500000us / (1/1.2us) --> 計數次數 LOAD s1, 27 LOAD s0, c0 jump software_delay software_delay: LOAD s0, s0 ;pad loop to make it 10 clock cycles (5 instructions), if clk 12MHz --> 1/1.2 us SUB s0, 01 SUBCY s1, 00 SUBCY s2, 00 JUMP NZ, software_delay RETURN
總結
個人水平有限,中斷部分過段時間在新增,對於實現簡單感測器的採集,已經足夠用了,導師抓得緊,牙縫裡擠出的時間寫的這個小專案,收穫了很多,現在這個專案只是模型,之後會逐步完善!