avalon总线与接口.docx
《avalon总线与接口.docx》由会员分享,可在线阅读,更多相关《avalon总线与接口.docx(23页珍藏版)》请在冰豆网上搜索。
avalon总线与接口
Avalon总线IP核的定制-----(深入了解软件编程的奥秘)
NIOSII是一个建立在FPGA上的嵌入式软核处理器,除了可以根据需要任意添加已经提供的外设外,用户还可以通过定制用户逻辑外设和定制用户指令来实现各种应用要求。
这节我们就来研究如何定制基于Avalon总线的用户外设。
SOPCBuilder提供了一个元件编辑器,通过这个元件编辑器我们就可以将我们自己写的逻辑封装成一个SOPCBuilder元件了。
下面,我们就以PWM实验为例,详细介绍一下定制基于Avalon总线的用户外设的过程。
我们要将的PWM是基于Avalon总线中的AvalonMemoryMappedInterface(Avalon-MM),而Avalon总线还有其他类型的设备,比如AvalonStreamingInterface(Avalon-ST)、AvalonMemoryMappedTristateInterface等等,在这里我就不详细叙述了,需要进一步了解的请参考ALTERA公司的《AvalonInterfaceSpecifications》(mnl_avalon_spec.pdf)。
Avalon-MM接口是内存映射系统下的用于主从设备之间的读写的接口,下图就是一个基于Avalon-MM的主从设备系统。
而我们这节需要做的就是下图高亮部分。
他的地位与UART,RAMController等模块并驾齐驱的。
Avalon-MM接口有很多特点,其中最大的特点就是根据自己的需求自由选择信号线,不过里面还是有一些要求的。
建议大家在看本文之前,先看一遍《AvalonInterfaceSpecifications》,这样就能对Avalon-MM接口有一个整体的了解。
下图为Avalon-MM外设的一个结构图,
理论的就说这些,下面我们举例来具体说明,让大家可以更好的理解。
构建HDL
我们这一节是PWM为例,所以首先,我们要构建一个符合Avalon-MMSlave接口规范的可以实现PWM功能的时序逻辑,在这里,我们利用Verilog语言来编写。
在程序中会涉及到Avalon信号,在这里,我们说明一下这些信号(其中,方向以从设备为基准)
HDL中的信号
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内部还包括使能控制寄存器、周期设定寄存器以及占空比设置寄存器。
设计中将各寄存器映射成AvalonSlave端口地址空间内一个单独的偏移地址。
没个寄存器都可以进行读写访问,软件可以读回寄存器中的当前值。
寄存器及偏移地址如下:
寄存器名
偏移量
访问属性
描述
clock_divide_reg
00
读/写
设定PWM输出周期的时钟数
duty_cycle_reg
01
读/写
设定一个周期内PWM输出低电平的始终个数
control_reg
10
读/写
使能和关闭PWM输出,为1时使能PWM输出
程序如下:
viewsource
print?
001
modulePWM(
002
clk,
003
reset_n,
004
chipselect,
005
address,
006
write,
007
writedata,
008
read,
009
byteenable,
010
readdata,
011
PWM_out);
012
013
inputclk;
014
inputreset_n;
015
inputchipselect;
016
input[1:
0]address;
017
inputwrite;
018
input[31:
0]writedata;
019
inputread;
020
input[3:
0]byteenable;
021
output[31:
0]readdata;
022
outputPWM_out;
023
024
reg[31:
0]clock_divide_reg;
025
reg[31:
0]duty_cycle_reg;
026
regcontrol_reg;
027
regclock_divide_reg_selected;
028
regduty_cycle_reg_selected;
029
regcontrol_reg_selected;
030
reg[31:
0]PWM_counter;
031
reg[31:
0]readdata;
032
regPWM_out;
033
wirepwm_enable;
034
035
//地址译码
036
always@(address)
037
begin
038
clock_divide_reg_selected<=0;
039
duty_cycle_reg_selected<=0;
040
control_reg_selected<=0;
041
case(address)
042
2'b00:
clock_divide_reg_selected<=1;
043
2'b01:
duty_cycle_reg_selected<=1;
044
2'b10:
control_reg_selected<=1;
045
default:
046
begin
047
clock_divide_reg_selected<=0;
048
duty_cycle_reg_selected<=0;
049
control_reg_selected<=0;
050
end
051
endcase
052
end
053
054
//写PWM输出周期的时钟数寄存器
055
always@(posedgeclkornegedgereset_n)
056
begin
057
if(reset_n==1'b0)
058
clock_divide_reg=0;
059
else
060
begin
061
if(write&chipselect&clock_divide_reg_selected)
062
begin
063
if(byteenable[0])
064
clock_divide_reg[7:
0]=writedata[7:
0];
065
if(byteenable[1])
066
clock_divide_reg[15:
8]=writedata[15:
8];
067
if(byteenable[2])
068
clock_divide_reg[23:
16]=writedata[23:
16];
069
if(byteenable[3])
070
clock_divide_reg[31:
24]=writedata[31:
24];
071
end
072
end
073
end
074
075
//写PWM周期占空比寄存器
076
always@(posedgeclkornegedgereset_n)
077
begin
078
if(reset_n==1'b0)
079
duty_cycle_reg=0;
080
else
081
begin
082
if(write&chipselect&duty_cycle_reg_selected)
083
begin
084
if(byteenable[0])
085
duty_cycle_reg[7:
0]=writedata[7:
0];
086
if(byteenable[1])
087
duty_cycle_reg[15:
8]=writedata[15:
8];
088
if(byteenable[2])
089
duty_cycle_reg[23:
16]=writedata[23:
16];
090
if(byteenable[3])
091
duty_cycle_reg[31:
24]=writedata[31:
24];
092
end
093
end
094
end
095
096
//写控制寄存器
097
always@(posedgeclkornegedgereset_n)
098
begin
099
if(reset_n==1'b0)
100
control_reg=0;
101
else
102
begin
103
if(write&chipselect&control_reg_selected)
104
begin
105
if(byteenable[0])
106
control_reg=writedata[0];
107
end
108
end
109
end
110
111
//读寄存器
112
always@(addressorreadorclock_divide_regorduty_cycle_regorcontrol_regorchipselect)
113
begin
114
if(read&chipselect)
115
case(address)
116
2'b00:
readdata<=clock_divide_reg;
117
2'b01:
readdata<=duty_cycle_reg;
118
2'b10:
readdata<=control_reg;
119
default:
readdata=32'h8888;
120
endcase
121
end
122
123
//控制寄存器
124
assignpwm_enable=control_reg;
125
126
//PWM功能部分
127
always@(posedgeclkornegedgereset_n)
128
begin
129
if(reset_n==1'b0)
130
PWM_counter=0;
131
else
132
begin
133
if(pwm_enable)
134
begin
135
if(PWM_counter>=clock_divide_reg)
136
PWM_counter<=0;
137
else
138
PWM_counter<=PWM_counter+1;
139
end
140
else
141
PWM_counter<=0;
142
end
143
end
144
145
always@(posedgeclkornegedgereset_n)
146
begin
147
if(reset_n==1'b0)
148
PWM_out<=1'b0;
149
else
150
begin
151
if(pwm_enable)
152
begin
153
if(PWM_counter<=duty_cycle_reg)
154
PWM_out<=1'b1;
155
else
156
PWM_out<=1'b0;
157
end
158
else
159
PWM_out<=1'b0;
160
end
161
end
162
163
endmodule
上面的程序保存好以后,命名为PWM.v,并将其存放到工程目录下。
硬件设置
接下来,我们就通过SOPCBuilder,来建立PWM模块了。
首先,打开Quartus软件,进入SOPCBuilder。
进入后,点击下图红圈处
点击后,如下图所示,点击Next,
点击后,如下图所示,点击下图红圈处,将我们刚才建立的PWM.v加进来。
(我将PWM。
v放到了工程目录下的pwm文件夹下)
加入后,系统会对PWM.v文件进行分析,如下图所示,出现红圈处的文字,说明分析成功,点击close,关闭对话框。
然后点击Next,如下图所示,通过下图,我们可以看到,PWM.v中的信号都出现在这里面了。
我们可以根据我们的功能要求来配置这些信号,其中,Interface是Avalon接口类型,它包括Avalon-MM、Avalon-ST、AvalonMemoryMappedTristateInterface等等。
SignalType指的是各个Avalon接口类型下的信号类型。
PWM.v中的信号我们已经在前面都介绍过了,大家按照上面的要求设置就可以了。
默认情况只有PWM_out需要改动,如下图示红圈处设置,
其中,Interface在下拉菜单中选择下图红圈处所示的选项。
上面的选项都设置好以后,点击Next,如下图所示,我们通过下图红圈处的下拉条向下拉
拉到下图所示位置停止,我们将红圈处的改选为NATIVE,这个地方就是地址对齐的选项,我们选择为静态地址对齐。
其他的地方都默认,不需要改动。
这里面还有很多选项,其中Timing部分需要说明一下,PWM的AvalonSlave端口与AvalonSlave端口时钟信号同步,读/写时的建立很保持时间为0,因为读、写寄存器仅需要一个时钟周期,所以读/写时为0等待切不需要读延时。
接着点击Next,如下图所示,其中红圈处需要注意,这个地方需要可以建立新组,然后在SOPCBuilder中体现出来。
点击Finish后,会出现下面的对话框,点击Yes,就会生成一个PWM_hw.tcl脚本文件,大家可以打开看一下,里面放置的是刚才我们配置PWM时候的配置信息。
上面都完成以后,我们回到了SOPCBuilder界面,我们在左侧边栏中可以找到下图所示的红圈处
大家看到了吧,MyIP就是我们刚才建立的group。
双击PWM,我们建立PWM模块,如下图
点击Finish,完成建立。
这里还需要设置一步,点击下图红圈处
点击后,如下图所示,点击IPSerarchPath,然后点击Add,添加PWM.v所在位置的路径
添加后,如下图所示
点击Finish完成。
设置这个选项是为了让SOPCBuilder可以找到PWM.v的位置。
不然就会出现下次你进入SOPCBuilder的时候PWM模块无效的问题。
接下来的工作就是自动分配地址,分配中断,编译,等待......
编译好以后,我们回到Quartus软件界面,我们可以看到,PWM出现了,我将它接到了一个LED上了,我们可以通过PWM改变LED的亮度,实现LED渐亮渐灭的过程。
接下来又是编译,等待.....
做好硬件部分工作以后,我们打开NIOSIDE,开始软件编程部分。
软件开发
首先对工程重新编译一次,Ctril+B,等待......
编译好以后,我们来看一下system.h的变化情况,我们可以发现,多出来PWM部分了。
下面是PWM测试代码,
viewsource
print?
01
#include
02
#include"system.h"
03
04
//根据寄存器的偏移量,我们定义一个结构体PWM
05
typedefstruct{
06
volatileunsignedintdivi;
07
volatileunsignedintduty;
08
volatileunsignedintenable;
09
}PWM;
10
11
intmain()
12
{
13
intdir=1;
14
15
//将pwm指向PWM_0_BASE首地址
16
PWM*pwm=(PWM*)PWM_0_BASE;
17
//对pwm进行初始化,divi最大值为232-1。
18
pwm->divi=1000;
19
pwm->duty=0;
20
pwm->enable=1;
21
22
//通过不断的改变duty值来改变LED一个周期亮灯的时间长短
23
while
(1){
24
if(dir>0){
25
if(pwm->dutydivi)
26
pwm->duty+=100;
27
else
28
dir=0;
29
}
30
else{
31
if(pwm->duty>0)
32
pwm->duty-=100;
33
else
34
dir=1;
35
}
36
37
usleep(100000);
38
}
39
40
return0;
41
}