1. 程式人生 > 實用技巧 >如何在FPGA上做一個沒啥用的mcu

如何在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 

總結

個人水平有限,中斷部分過段時間在新增,對於實現簡單感測器的採集,已經足夠用了,導師抓得緊,牙縫裡擠出的時間寫的這個小專案,收穫了很多,現在這個專案只是模型,之後會逐步完善!