天天進步一點點------SOPC的Avalon-MM IP核(二) AVALON總線的IP覈定制

簡介

      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

  理論的就說這些,下面咱們舉例來具體說明,讓你們能夠更好的理解。測試

構建HDL

      咱們這一節是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    }
相關文章
相關標籤/搜索