基于AVALON总线的IP核定制 PWM
简介
NIOS II是一个建立在FPGA上的嵌入式软核处理器,除了可以根据需要任意添加已经提供的外设外,用户还可以通过定制用户逻辑外设和定制用户指令来实现各种应用要求。这节我们就来研究如何定制基于Avalon总线的用户外设。
SOPC Builder提供了一个元件编辑器,通过这个元件编辑器我们就可以将我们自己写的逻辑封装成一个SOPC Builder元件了。下面,我们就以PWM实验为例,详细介绍一下定制基于Avalon总线的用户外设的过程。
我们要将的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等模块并驾齐驱的。
Avalon-MM接口有很多特点,其中最大的特点就是根据自己的需求自由选择信号线,不过里面还是有一些要求的。建议大家在看本文之前,先看一遍《Avalon Interface Specifications》,这样就能对Avalon-MM接口有一个整体的了解。
下图为Avalon-MM外设的一个结构图,
理论的就说这些,下面我们举例来具体说明,让大家可以更好的理解。
构建HDL
我们这一节是PWM为例,所以首先,我们要构建一个符合Avalon-MM Slave接口规范的可以实现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内部还包括使能控制寄存器、周期设定寄存器以及占空比设置寄存器。设计中将各寄存器映射成Avalon Slave端口地址空间内一个单独的偏移地址。没个寄存器都可以进行读写访问,软件可以读回寄存器中的当前值。寄存器及偏移地址如下:
寄存器名 |
偏移量 |
访问属性 |
描述 |
clock_divide_reg |
00 |
读/写 |
设定PWM输出周期的时钟数 |
duty_cycle_reg |
01 |
读/写 |
设定一个周期内PWM输出低电平的始终个数 |
control_reg |
10 |
读/写 |
使能和关闭PWM输出,为1时使能PWM输出 |
程序如下:
001
|
module PWM(
|
002
|
clk,
|
003
|
reset_n,
|
004
|
chipselect,
|
005
|
address,
|
006
|
write,
|
007
|
writedata,
|
008
|
read,
|
009
|
byteenable,
|
010
|
readdata,
|
011
|
PWM_out);
|
012
|
|
013
|
input clk;
|
014
|
input reset_n;
|
015
|
input chipselect;
|
016
|
input [1:0]address;
|
017
|
input write;
|
018
|
input [31:0] writedata;
|
019
|
input read;
|
020
|
input [3:0] byteenable;
|
021
|
output [31:0] readdata;
|
022
|
output PWM_out;
|
023
|
|
024
|
reg [31:0] clock_divide_reg;
|
025
|
reg [31:0] duty_cycle_reg;
|
026
|
reg control_reg;
|
027
|
reg clock_divide_reg_selected;
|
028
|
reg duty_cycle_reg_selected;
|
029
|
reg control_reg_selected;
|
030
|
reg [31:0] PWM_counter;
|
031
|
reg [31:0] readdata;
|
032
|
reg PWM_out;
|
033
|
wire pwm_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 @ (posedge clk or negedge reset_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 @ (posedge clk or negedge reset_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 @ (posedge clk or negedge reset_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 @ (address or read or clock_divide_reg or duty_cycle_reg or control_reg or chipselect)
|
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
|
assign pwm_enable=control_reg;
|
125
|
|
126
|
//PWM功能部分
|
127
|
always @ (posedge clk or negedge reset_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 @ (posedge clk or negedge reset_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,并将其存放到工程目录下。
硬件设置
接下来,我们就通过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测试代码,
01
|
#include <unistd.h>
|
02
|
#include "system.h"
|
03
|
|
04
|
//根据寄存器的偏移量,我们定义一个结构体PWM
|
05
|
typedef struct {
|
06
|
volatile unsigned int divi;
|
07
|
volatile unsigned int duty;
|
08
|
volatile unsigned int enable;
|
09
|
}PWM;
|
10
|
|
11
|
int main()
|
12
|
{
|
13
|
int dir = 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->duty < pwm->divi)
|
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
|
return 0;
|
41
|
}
|
基于AVALON总线的IP核定制 PWM相关推荐
- niosII的那些事--基于AVALON总线的IP核定制
简介 NIOS II是一个建立在FPGA上的嵌入式软核处理器,除了可以根据需要任意添加已经提供的外设外,用户还可以通过定制用户逻辑外设和定制用户指令来实现各种应用要求.这节我们就来研究如何定制基于Av ...
- 【FPGA黑金开发板】NIOSII那些事儿--基于AVALON总线的IP定制(十七)
声明:本文为转载作品,版权归本博文作者所有,如需转载,请注明出处http://www.cnblogs.com/kingst/ 简介 NIOS II是一个建立在FPGA上的嵌入式软核处理器,除了可以根据 ...
- FPGA逻辑设计回顾(13)RAM以及ROM的IP核定制以及关键参数
文章目录 前言 RAM IP的定制 Xilinx的IP定制位置 Block RAM的定制过程 第一页 第二页 第三页 第四页 第五页 Block RAM的延迟讨论 ROM IP核的定制 总结 前言 本 ...
- 开发自定义AXI总线外设IP核——以LED和开关为例
http://www.eefocus.com/nightseas/blog/12-10/287343_15762.html ZedBoard学习手记(二) 开发自定义AXI总线外设IP核--以LED和 ...
- 如何基于Avalon总线完成QSYS IP定制
文章目录 0. 为什么要定制QSYS IP 1. 规划IP的硬件功能 2. 定义恰当的Avalon接口 3. RTL设计 4. 使用IP编辑器封装IP 5. 编写用于描述寄存器的C头文件和IP驱动文件 ...
- 基于vivado的fir ip核的重采样设计与实现
创建vivado工程 1. 首先打开vivado,创建一个新的project(勾选create project subdirectory选项),并将工程命填为firfilter. 2.选择工程创建的类 ...
- 基于ISE的QDR IP核调用与硬件自检
平台:ISE(IP核用法同VIVADO) 语言:VHDL(Verilog用法类似) FPGA型号:V6-315T,ffg1156-1 QDR型号:GS8342D08GE-300I(类似) XILINX ...
- FPGA设计心得(9)基于DDS IP核的任意波形发生器设计
博文目录 写在前面 正文 设计要求 IP核配置 定制输出数据位宽 定制相位位宽(或频率分辨率) 输出频率 输出正余弦选择以及数据格式 其他设置 电路设计 行为仿真 参考资料 交个朋友 写在前面 数据手 ...
- 通过Clocking Wizard定制和生成一个IP核(MMCM)(Virtex7)(ISE版)
目录 定制过程 准备进入定制页面 第一页 Clocking features 第二页 第三页 Selecting Optional Ports 第四页 第五页 第六页 定制过程 准备进入定制页面 首先 ...
最新文章
- Xgboost调参小结
- python学习之 字符串前'r'的用法
- mysql主主同步配置_MySQL 主主同步配置步骤
- Codeforces Round #650 (Div. 3)(A-C)
- 滴滴升级“极速拼车”:未拼成可享折扣 拼成更便宜
- 每天一道剑指offer-对称的二叉树
- 【离散数学】集合论 第四章 函数与集合(6) 三歧性定理、两集合基数判等定理(基数的比较)、Cantor定理
- java课程设计小组分工_java课程设计---团体
- 用 Java 实现的八种常用排序算法
- 机器学习笔记之概率图模型(八)信念传播(Belief Propagation,BP)(基于树结构)
- 使用Snakemake搭建分析流程
- 2022中国开发者影响力年度榜单揭晓,华为、阿里、腾讯等入选年度开源贡献企业 | 美通社头条...
- 使用python实现用微信远程控制电脑
- Django入门(一)
- 13款用于拍摄全景照片的iOS应用
- TA-Lib介绍安装及使用教程
- GEE哨兵二号去云不成功的原因(代码修改)
- 剑指Offer(十九):顺时针打印矩阵
- 造梦无双服务器维护12月17日,造梦无双Online官网版
- SVN使用教程(Linux)
热门文章
- 说说学习中的那点小焦虑
- C语言保留小数相关问题
- vue el-input的多种验证
- 雷柏MT750S鼠标使用总结(驱动|连接|模式|续航)
- Python中的bytes与bytearray详解
- 苹果 App Store账号申请和证书申请发布app
- 《MySQL必修课:存储引擎大揭秘!InnoDB和MyISAM究竟谁更强?》
- [4G5G专题-51]:物理层-正交频分复用OFDM, AxC, IQ,
- uaa权限scope
- Markdown快速入门和扩展内容(Typora)