NIOS II是一個創建在FPGA上的嵌入式軟核處理器,除了能夠根據須要任意添加已經提供的外設外,用戶還能夠經過定製用戶邏輯外設和定製用戶指令來實現各類應用要求。這節咱們就來研究如何定製基於Avalon總線的用戶外設。ios
SOPC Builder提供了一個元件編輯器,經過這個元件編輯器咱們就能夠將咱們本身寫的邏輯封裝成一個SOPC Builder元件了。下面,咱們就以PWM實驗爲例,詳細介紹一下定製基於Avalon總線的用戶外設的過程。web
咱們要將的PWM是基於Avalon總線中的Avalon Memory Mapped Interface (Avalon-MM),而Avalon總線還有其餘類型的設備,好比Avalon Streaming Interface (Avalon-ST)、Avalon Memory Mapped Tristate Interface等等,在這裏我就不詳細敘述了,須要進一步瞭解的請參考ALTERA公司的《Avalon Interface Specifications》(mnl_avalon_spec.pdf)。編程
Avalon-MM接口是內存映射系統下的用於主從設備之間的讀寫的接口,下圖就是一個基於Avalon-MM的主從設備系統。而咱們這節須要作的就是下圖高亮部分。他的地位與UART,RAM Controller等模塊並駕齊驅的。app
Avalon-MM接口有不少特色,其中最大的特色就是根據本身的需求自由選擇信號線,不過裏面仍是有一些要求的。建議你們在看本文以前,先看一遍《Avalon Interface Specifications》,這樣就能對Avalon-MM接口有一個總體的瞭解。編輯器
下圖爲Avalon-MM外設的一個結構圖,ide
理論的就說這些,下面咱們舉例來具體說明,讓你們能夠更好的理解。測試
咱們這一節是PWM爲例,因此首先,咱們要構建一個符合Avalon-MM Slave接口規範的能夠實現PWM功能的時序邏輯,在這裏,咱們利用Verilog語言來編寫。在程序中會涉及到Avalon信號,在這裏,咱們說明一下這些信號(其中,方向以從設備爲基準)ui
HDL中的信號 spa |
Avalon信號類型 設計 |
寬度 |
方向 |
描述 |
clk |
clk |
1 |
input |
同步時鐘信號 |
reset_n |
reset_n |
1 |
input |
復位信號,低電平有效 |
chipselect |
chipselect |
1 |
input |
片選信號 |
address |
address |
2 |
input |
2位地址,譯碼後肯定寄存器offset |
write |
write |
1 |
input |
寫使能信號 |
writedata |
writedata |
32 |
input |
32位寫數據值 |
read |
read |
1 |
input |
讀時能信號 |
byteenable |
byteenable |
1 |
input |
字節使能信號 |
readdata |
readdata |
32 |
output |
32位讀數據值 |
此外,程序中還包括一個PWM_out信號,這個信號是PWM輸出,不屬於Avalon接口信號。
PWM內部還包括使能控制寄存器、週期設定寄存器以及佔空比設置寄存器。設計中將各寄存器映射成Avalon Slave端口地址空間內一個單獨的偏移地址。沒個寄存器均可以進行讀寫訪問,軟件能夠讀回寄存器中的當前值。寄存器及偏移地址以下:
寄存器名 |
偏移量 |
訪問屬性 |
描述 |
clock_divide_reg |
00 |
讀/寫 |
設定PWM輸出週期的時鐘數 |
duty_cycle_reg |
01 |
讀/寫 |
設定一個週期內PWM輸出低電平的始終個數 |
control_reg |
10 |
讀/寫 |
使能和關閉PWM輸出,爲1時使能PWM輸出 |
程序以下:
1 001 module PWM( 2 002 clk, 3 003 reset_n, 4 004 chipselect, 5 005 address, 6 006 write, 7 007 writedata, 8 008 read, 9 009 byteenable, 10 010 readdata, 11 011 PWM_out); 12 012 13 013 input clk; 14 014 input reset_n; 15 015 input chipselect; 16 016 input [1:0]address; 17 017 input write; 18 018 input [31:0] writedata; 19 019 input read; 20 020 input [3:0] byteenable; 21 021 output [31:0] readdata; 22 022 output PWM_out; 23 023 24 024 reg [31:0] clock_divide_reg; 25 025 reg [31:0] duty_cycle_reg; 26 026 reg control_reg; 27 027 reg clock_divide_reg_selected; 28 028 reg duty_cycle_reg_selected; 29 029 reg control_reg_selected; 30 030 reg [31:0] PWM_counter; 31 031 reg [31:0] readdata; 32 032 reg PWM_out; 33 033 wire pwm_enable; 34 034 35 035 //地址譯碼 36 036 always @ (address) 37 037 begin 38 038 clock_divide_reg_selected<=0; 39 039 duty_cycle_reg_selected<=0; 40 040 control_reg_selected<=0; 41 041 case(address) 42 042 2'b00:clock_divide_reg_selected<=1; 43 043 2'b01:duty_cycle_reg_selected<=1; 44 044 2'b10:control_reg_selected<=1; 45 045 default: 46 046 begin 47 047 clock_divide_reg_selected<=0; 48 048 duty_cycle_reg_selected<=0; 49 049 control_reg_selected<=0; 50 050 end 51 051 endcase 52 052 end 53 053 54 054 //寫PWM輸出週期的時鐘數寄存器 55 055 always @ (posedge clk or negedge reset_n) 56 056 begin 57 057 if(reset_n==1'b0) 58 058 clock_divide_reg=0; 59 059 else 60 060 begin 61 061 if(write & chipselect & clock_divide_reg_selected) 62 062 begin 63 063 if(byteenable[0]) 64 064 clock_divide_reg[7:0]=writedata[7:0]; 65 065 if(byteenable[1]) 66 066 clock_divide_reg[15:8]=writedata[15:8]; 67 067 if(byteenable[2]) 68 068 clock_divide_reg[23:16]=writedata[23:16]; 69 069 if(byteenable[3]) 70 070 clock_divide_reg[31:24]=writedata[31:24]; 71 071 end 72 072 end 73 073 end 74 074 75 075 //寫PWM週期佔空比寄存器 76 076 always @ (posedge clk or negedge reset_n) 77 077 begin 78 078 if(reset_n==1'b0) 79 079 duty_cycle_reg=0; 80 080 else 81 081 begin 82 082 if(write & chipselect & duty_cycle_reg_selected) 83 083 begin 84 084 if(byteenable[0]) 85 085 duty_cycle_reg[7:0]=writedata[7:0]; 86 086 if(byteenable[1]) 87 087 duty_cycle_reg[15:8]=writedata[15:8]; 88 088 if(byteenable[2]) 89 089 duty_cycle_reg[23:16]=writedata[23:16]; 90 090 if(byteenable[3]) 91 091 duty_cycle_reg[31:24]=writedata[31:24]; 92 092 end 93 093 end 94 094 end 95 095 96 096 //寫控制寄存器 97 097 always @ (posedge clk or negedge reset_n) 98 098 begin 99 099 if(reset_n==1'b0) 100 100 control_reg=0; 101 101 else 102 102 begin 103 103 if(write & chipselect & control_reg_selected) 104 104 begin 105 105 if(byteenable[0]) 106 106 control_reg=writedata[0]; 107 107 end 108 108 end 109 109 end 110 110 111 111 //讀寄存器 112 112 always @ (address or read or clock_divide_reg or duty_cycle_reg or control_reg or chipselect) 113 113 begin 114 114 if(read & chipselect) 115 115 case(address) 116 116 2'b00:readdata<=clock_divide_reg; 117 117 2'b01:readdata<=duty_cycle_reg; 118 118 2'b10:readdata<=control_reg; 119 119 default:readdata=32'h8888; 120 120 endcase 121 121 end 122 122 123 123 //控制寄存器 124 124 assign pwm_enable=control_reg; 125 125 126 126 //PWM功能部分 127 127 always @ (posedge clk or negedge reset_n) 128 128 begin 129 129 if(reset_n==1'b0) 130 130 PWM_counter=0; 131 131 else 132 132 begin 133 133 if(pwm_enable) 134 134 begin 135 135 if(PWM_counter>=clock_divide_reg) 136 136 PWM_counter<=0; 137 137 else 138 138 PWM_counter<=PWM_counter+1; 139 139 end 140 140 else 141 141 PWM_counter<=0; 142 142 end 143 143 end 144 144 145 145 always @ (posedge clk or negedge reset_n) 146 146 begin 147 147 if(reset_n==1'b0) 148 148 PWM_out<=1'b0; 149 149 else 150 150 begin 151 151 if(pwm_enable) 152 152 begin 153 153 if(PWM_counter<=duty_cycle_reg) 154 154 PWM_out<=1'b1; 155 155 else 156 156 PWM_out<=1'b0; 157 157 end 158 158 else 159 159 PWM_out<=1'b0; 160 160 end 161 161 end 162 162 163 163 endmodule
上面的程序保存好之後,命名爲PWM.v,並將其存放到工程目錄下。
接下來,咱們就經過SOPC Builder,來創建PWM模塊了。首先,打開Quartus軟件,進入SOPC Builder。進入後,點擊下圖紅圈處
點擊後,以下圖所示,點擊Next,
點擊後,以下圖所示,點擊下圖紅圈處,將咱們剛纔創建的PWM.v加進來。(我將PWM。v放到了工程目錄下的pwm文件夾下)
加入後,系統會對PWM.v文件進行分析,以下圖所示,出現紅圈處的文字,說明分析成功,點擊close,關閉對話框。
而後點擊Next,以下圖所示,經過下圖,咱們能夠看到,PWM.v中的信號都出如今這裏面了。咱們能夠根據咱們的功能要求來配置這些信號,其中,Interface是Avalon接口類型 ,它包括Avalon-MM、Avalon-ST、Avalon Memory Mapped Tristate Interface等等。Signal Type指的是各個Avalon接口類型下的信號類型。PWM.v中的信號咱們已經在前面都介紹過了,你們按照上面的要求設置就能夠了。默認狀況只有PWM_out須要改動,以下圖示紅圈處設置,
其中,Interface在下拉菜單中選擇下圖紅圈處所示的選項。
上面的選項都設置好之後,點擊Next,以下圖所示,咱們經過下圖紅圈處的下拉條向下拉
拉到下圖所示位置中止,咱們將紅圈處的改選爲NATIVE,這個地方就是地址對齊的選項,咱們選擇爲靜態地址對齊。其餘的地方都默認,不須要改動。
這裏面還有不少選項,其中Timing部分須要說明一下,PWM的Avalon Slave端口與Avalon Slave端口時鐘信號同步,讀/寫時的創建很保持時間爲0,由於讀、寫寄存器僅須要一個時鐘週期,因此讀/寫時爲0等待切不須要讀延時。
接着點擊Next,以下圖所示,其中紅圈處須要注意,這個地方須要能夠創建新組,而後在SOPC Builder中體現出來。
點擊Finish後,會出現下面的對話框,點擊Yes,就會生成一個PWM_hw.tcl腳本文件,你們能夠打開看一下,裏面放置的是剛纔咱們配置PWM時候的配置信息。
上面都完成之後,咱們回到了SOPC Builder界面,咱們在左側邊欄中能夠找到下圖所示的紅圈處
你們看到了吧,MyIP就是咱們剛纔創建的group。雙擊PWM,咱們創建PWM模塊,以下圖
點擊Finish,完成創建。
這裏還須要設置一步,點擊下圖紅圈處
點擊後,以下圖所示,點擊IP Serarch Path,而後點擊Add,添加PWM.v所在位置的路徑
添加後,以下圖所示
點擊Finish完成。設置這個選項是爲了讓SOPC Builder能夠找到PWM.v的位置。否則就會出現下次你進入SOPC Builder的時候PWM模塊無效的問題。
接下來的工做就是自動分配地址,分配中斷,編譯,等待......
編譯好之後,咱們回到Quartus軟件界面,咱們能夠看到,PWM出現了,我將它接到了一個LED上了,咱們能夠經過PWM改變LED的亮度,實現LED漸亮漸滅的過程。
接下來又是編譯,等待.....
作好硬件部分工做之後,咱們打開NIOS IDE,開始軟件編程部分。
首先對工程從新編譯一次,Ctril+B,等待......
編譯好之後,咱們來看一下system.h的變化狀況,咱們能夠發現,多出來PWM部分了。
下面是PWM測試代碼,
1 01 #include <unistd.h> 2 02 #include "system.h" 3 03 4 04 //根據寄存器的偏移量,咱們定義一個結構體PWM 5 05 typedef struct{ 6 06 volatile unsigned int divi; 7 07 volatile unsigned int duty; 8 08 volatile unsigned int enable; 9 09 }PWM; 10 10 11 11 int main() 12 12 { 13 13 int dir = 1; 14 14 15 15 //將pwm指向PWM_0_BASE首地址 16 16 PWM *pwm = (PWM *)PWM_0_BASE; 17 17 //對pwm進行初始化,divi最大值爲232-1。 18 18 pwm->divi = 1000; 19 19 pwm->duty = 0; 20 20 pwm->enable = 1; 21 21 22 22 //經過不斷的改變duty值來改變LED一個週期亮燈的時間長短 23 23 while(1){ 24 24 if(dir > 0){ 25 25 if(pwm->duty < pwm->divi) 26 26 pwm->duty += 100; 27 27 else 28 28 dir = 0; 29 29 } 30 30 else{ 31 31 if(pwm->duty > 0) 32 32 pwm->duty -= 100; 33 33 else 34 34 dir = 1; 35 35 } 36 36 37 37 usleep(100000); 38 38 } 39 39 40 40 return 0; 41 41 }