转:在SOPC定制自己的IP

http://www.cnblogs.com/kingst/archive/2010/06/05/1752363.html

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等模块并驾齐驱的。

clip_image002

      Avalon-MM接口有很多特点,其中最大的特点就是根据自己的需求自由选择信号线,不过里面还是有一些要求的。建议大家在看本文之前,先看一遍《Avalon Interface Specifications》,这样就能对Avalon-MM接口有一个整体的了解。

      下图为Avalon-MM外设的一个结构图,

clip_image004

      理论的就说这些,下面我们举例来具体说明,让大家可以更好的理解。

构建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输出

程序如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
module PWM(
     clk,
     reset_n,
     chipselect,
     address,
     write,
     writedata,
     read,
     byteenable,
     readdata,
     PWM_out);
  
input clk;
input reset_n;
input chipselect;
input [1:0]address;
input write;
input [31:0] writedata;
input read;
input [3:0] byteenable;
output [31:0] readdata;
output PWM_out;
  
reg [31:0] clock_divide_reg; 
reg [31:0] duty_cycle_reg; 
reg control_reg;
reg clock_divide_reg_selected;
reg duty_cycle_reg_selected;
reg control_reg_selected;
reg [31:0] PWM_counter;
reg [31:0] readdata;
reg PWM_out;
wire pwm_enable;
  
//地址译码
always @ (address)
begin
     clock_divide_reg_selected<=0;
     duty_cycle_reg_selected<=0;
     control_reg_selected<=0;
     case (address)
         2'b00:clock_divide_reg_selected<=1;
         2'b01:duty_cycle_reg_selected<=1;
         2'b10:control_reg_selected<=1;
         default :
         begin
             clock_divide_reg_selected<=0;
             duty_cycle_reg_selected<=0;
             control_reg_selected<=0;
         end
     endcase
end           
  
//写PWM输出周期的时钟数寄存器
always @ (posedge clk or negedge reset_n)
begin
     if (reset_n==1'b0)
         clock_divide_reg=0;
     else
     begin
         if (write & chipselect & clock_divide_reg_selected)
         begin
             if (byteenable[0])
                 clock_divide_reg[7:0]=writedata[7:0];
             if (byteenable[1])
                 clock_divide_reg[15:8]=writedata[15:8];
             if (byteenable[2])
                 clock_divide_reg[23:16]=writedata[23:16];
             if (byteenable[3])
                 clock_divide_reg[31:24]=writedata[31:24];
         end
     end
end
  
//写PWM周期占空比寄存器
always @ (posedge clk or negedge reset_n)
begin
     if (reset_n==1'b0)
         duty_cycle_reg=0;
     else
     begin
         if (write & chipselect & duty_cycle_reg_selected)
         begin
             if (byteenable[0])
                 duty_cycle_reg[7:0]=writedata[7:0];
             if (byteenable[1])
                 duty_cycle_reg[15:8]=writedata[15:8];
             if (byteenable[2])
                 duty_cycle_reg[23:16]=writedata[23:16];
             if (byteenable[3])
                 duty_cycle_reg[31:24]=writedata[31:24];
         end
     end
end
  
//写控制寄存器
always @ (posedge clk or negedge reset_n)
begin
     if (reset_n==1'b0)
         control_reg=0;
     else
     begin
         if (write & chipselect & control_reg_selected)
         begin
             if (byteenable[0])
                 control_reg=writedata[0];
         end
     end
end
  
//读寄存器
always @ (address or read or clock_divide_reg or duty_cycle_reg or control_reg or chipselect)
begin
     if (read & chipselect)
         case (address)
             2'b00:readdata<=clock_divide_reg;
             2'b01:readdata<=duty_cycle_reg;
             2'b10:readdata<=control_reg;
             default :readdata=32'h8888;
         endcase 
end
  
//控制寄存器
assign pwm_enable=control_reg;
  
//PWM功能部分
always @ (posedge clk or negedge reset_n)
begin
     if (reset_n==1'b0)
         PWM_counter=0;
     else
     begin
         if (pwm_enable)
         begin
             if (PWM_counter>=clock_divide_reg)
                 PWM_counter<=0;
             else
                 PWM_counter<=PWM_counter+1;
         end
         else
             PWM_counter<=0;
     end
end      
  
always @ (posedge clk or negedge reset_n)
begin
     if (reset_n==1'b0)
         PWM_out<=1'b0;
     else
     begin
         if (pwm_enable)
         begin
             if (PWM_counter<=duty_cycle_reg)
                 PWM_out<=1'b1;
             else
                 PWM_out<=1'b0;
         end
         else
             PWM_out<=1'b0;
     end
end
  
endmodule

上面的程序保存好以后,命名为PWM.v,并将其存放到工程目录下。

硬件设置

      接下来,我们就通过SOPC Builder,来建立PWM模块了。首先,打开Quartus软件,进入SOPC Builder。进入后,点击下图红圈处

clip_image006

点击后,如下图所示,点击Next,

clip_image008

点击后,如下图所示,点击下图红圈处,将我们刚才建立的PWM.v加进来。(我将PWM。v放到了工程目录下的pwm文件夹下)

clip_image010

加入后,系统会对PWM.v文件进行分析,如下图所示,出现红圈处的文字,说明分析成功,点击close,关闭对话框。

clip_image012

然后点击Next,如下图所示,通过下图,我们可以看到,PWM.v中的信号都出现在这里面了。我们可以根据我们的功能要求来配置这些信号,其中,Interface是Avalon接口类型 ,它包括Avalon-MM、Avalon-ST、Avalon Memory Mapped Tristate Interface等等。Signal Type指的是各个Avalon接口类型下的信号类型。PWM.v中的信号我们已经在前面都介绍过了,大家按照上面的要求设置就可以了。默认情况只有PWM_out需要改动,如下图示红圈处设置,

clip_image014

其中,Interface在下拉菜单中选择下图红圈处所示的选项。

clip_image016

上面的选项都设置好以后,点击Next,如下图所示,我们通过下图红圈处的下拉条向下拉

clip_image018

拉到下图所示位置停止,我们将红圈处的改选为NATIVE,这个地方就是地址对齐的选项,我们选择为静态地址对齐。其他的地方都默认,不需要改动。

clip_image020

      这里面还有很多选项,其中Timing部分需要说明一下,PWM的Avalon Slave端口与Avalon Slave端口时钟信号同步,读/写时的建立很保持时间为0,因为读、写寄存器仅需要一个时钟周期,所以读/写时为0等待切不需要读延时。

接着点击Next,如下图所示,其中红圈处需要注意,这个地方需要可以建立新组,然后在SOPC Builder中体现出来。

clip_image022

点击Finish后,会出现下面的对话框,点击Yes,就会生成一个PWM_hw.tcl脚本文件,大家可以打开看一下,里面放置的是刚才我们配置PWM时候的配置信息。

clip_image024

上面都完成以后,我们回到了SOPC Builder界面,我们在左侧边栏中可以找到下图所示的红圈处

clip_image026

大家看到了吧,MyIP就是我们刚才建立的group。双击PWM,我们建立PWM模块,如下图

clip_image028

点击Finish,完成建立。

这里还需要设置一步,点击下图红圈处

clip_image030

点击后,如下图所示,点击IP Serarch Path,然后点击Add,添加PWM.v所在位置的路径

clip_image032

添加后,如下图所示

clip_image034

      点击Finish完成。设置这个选项是为了让SOPC Builder可以找到PWM.v的位置。不然就会出现下次你进入SOPC Builder的时候PWM模块无效的问题。

接下来的工作就是自动分配地址,分配中断,编译,等待......

      编译好以后,我们回到Quartus软件界面,我们可以看到,PWM出现了,我将它接到了一个LED上了,我们可以通过PWM改变LED的亮度,实现LED渐亮渐灭的过程。

clip_image036

接下来又是编译,等待.....

      做好硬件部分工作以后,我们打开NIOS IDE,开始软件编程部分。

软件开发

      首先对工程重新编译一次,Ctril+B,等待......

      编译好以后,我们来看一下system.h的变化情况,我们可以发现,多出来PWM部分了。

 

下面是PWM测试代码,

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <unistd.h>
#include "system.h"
  
//根据寄存器的偏移量,我们定义一个结构体PWM
typedef struct {
     volatile unsigned int divi;
     volatile unsigned int duty;
     volatile unsigned int enable;
}PWM;
  
int main()
{
int dir = 1;
  
     //将pwm指向PWM_0_BASE首地址
PWM *pwm = (PWM *)PWM_0_BASE;
//对pwm进行初始化,divi最大值为232-1。
     pwm->divi = 1000;
     pwm->duty = 0;
     pwm->enable = 1;
   
     //通过不断的改变duty值来改变LED一个周期亮灯的时间长短
     while (1){
         if (dir > 0){
             if (pwm->duty < pwm->divi)
                 pwm->duty += 100;
             else
                 dir = 0;
         }
         else {
             if (pwm->duty > 0)
                 pwm->duty -= 100;
             else
                 dir = 1;
         }
          
         usleep(100000);
     }
      
     return 0;   
}

转载于:https://www.cnblogs.com/GL-BBL/archive/2012/08/21/2649927.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值