学会自己定制简单的IP

学习了很长时间的avolon 总线无果,迷茫中,很难搞清楚到底怎么学习了IP的构建,先马马虎虎的跟着别人做做试试

在定制之前,得打开之前的工程文件夹看看你之前的nios II工程,比如做了LED显示的时候,用到了PIO的IP,找到了LED4.V(SOPC中定制硬件时候起的名字),打开后看到了下面的代码部分

module led4 (

// inputs:

address,

chipselect,

clk,

reset_n,

write_n,

writedata,



// outputs:

out_port,

readdata

)

;



output [ 3: 0] out_port;

output [ 3: 0] readdata;

input [ 1: 0] address;

input chipselect;

input clk;

input reset_n;

input write_n;

input [ 3: 0] writedata;



wire clk_en;

reg [ 3: 0] data_out;

wire [ 3: 0] out_port;

wire [ 3: 0] read_mux_out;

wire [ 3: 0] readdata;

assign clk_en = 1;

//s1, which is an e_avalon_slave

assign read_mux_out = {4 {(address == 0)}} & data_out;

always @(posedge clk or negedge reset_n)

begin

if (reset_n == 0)

data_out <= 0;

else if (chipselect && ~write_n && (address == 0))

data_out <= writedata[3 : 0];

end





assign readdata = read_mux_out;

assign out_port = data_out;



endmodule


 这是在设置了SOPC以后,generate出来的关于PIO IP核的verilog代码,所以算是一个官方的IP核,代码并不是很好读。但是其风格应该是我们模仿的不二选,因为毕竟是QUARTUS II自己生成的代码。

对照avalon 总线时序,应该明白这是最简单的基本读写时序。当然,这个程序的地址译码和寄存器的操作部分是在一起的。所以并不是很好读,也许是过于简单的原因吧,没有用到strobe的变量。但是再打开复杂一点的程序,就会发现有strobe这个单词了。它就是闸门的意思,如在我的外部中断生成的IP中,就有这么一句:

edge_capture_wr_strobe = chipselect && ~write_n && (address == 3);

这中间就有了strobe。这就是一个门,门的钥匙就是address == 3。这个门是通向了写edge_capture这个房间的。这样我们就明白了很多吧,对于address的理解是对哪个寄存器进行操作的。所以在很多资源中,我们都看到了把address也叫做offset.这个offset至关重要,如果知道在做工程的时候在system.h文件中的**_BASE算最重要的话,那这个offset应该在软硬件里面必须是第二重要的吧,因为这个offset就是在BASE的基础上的偏倚对寄存器的操作。我们如果对寄存器的操作还只是停留在使用

#include "altera_avalon_pio_regs.h"  的话,那现在我们就可以更进一步的观察了。试着找到这个altera_avalon_pio_regs.h。

这个.H文件的路径非常的深。我从

C:\altera\90\ip\altera\sopc_builder_ip\altera_avalon_pio\inc

这个路径下把它挖了出来,看到了这样一句定义:

#define IOWR_ALTERA_AVALON_PIO_DATA(base, data)      IOWR(base, 0, data)

原来他也只是一句宏定义,并不是一个函数什么的。但是们看到后面的一句IOWR(base, 0, data时就又生好奇,继续把这句话的来源揪出来。

这个路径依然很深

C: \altera\90\nios2eds\components\altera_nios2\HAL\inc 

我们再看到这样一句定义:

#define IOWR(BASE, REGNUM, DATA)  

  __builtin_stwio (__IO_CALC_ADDRESS_NATIVE ((BASE), (REGNUM)), (DATA)) 

  这个(REGNUM)就是我们的offset,也就是address。其实不必知道这么多,只要理解IOWR(base, 0, data)的参数0并不是一个随便的数字,它在硬件上对应的含义就是我们定义的address。那一切都明白的差不多了。

但是这个程序中只有一个寄存器,完成读写任务只需要一个offset,为什么这里定义了两位的addres,就不懂了。

知道了这些,就可以读懂了别人的IP了,当然也可以自己马上尝试自己构建IP。

下面是我自己的构建的IP实现PWM输出的功能,当然这个代码已经有了很多版本,我也看过了黑金发布的。我这里主要是模仿了quartus II的风格,代码如下:

module pwm (
//inputs
clk,
reset_n,
chipselect,
address,
write_n,
writedata,
readdata,
//outputs
PWM_out
)
;

input clk;
input reset_n;
input chipselect;
input [1:0]address;
input write_n;
input [31:0]writedata;
output [31:0]readdata;
output PWM_out;

reg [31:0] PWM_period_reg;
reg [31:0] PWM_duty_reg;
wire [31:0] readdata;
reg PWM_enable;
reg PWM_out_reg;
wire PWM_period_wr_strobe;
wire PWM_duty_wr_strobe;
wire PWM_enable_wr_strobe;
wire [31:0] read_mux_out;
wire PWM_control;
reg [31:0] PWM_counter;
//read register
assign read_mux_out=({32{(address==0)}}&PWM_period_reg|
{32{(address==1)}}&PWM_duty_reg|
{32{(address==2)}}&PWM_enable);

assign readdata = read_mux_out;



//operate PWM_period_reg
assign PWM_period_wr_strobe = chipselect && ~write_n && (address == 0);
always @(posedge clk or negedge reset_n)
begin
if (reset_n == 0)
PWM_period_reg=0;
else if (PWM_period_wr_strobe)
PWM_period_reg<=writedata;
end

//operate PWM_duty_reg
assign PWM_duty_wr_strobe= chipselect && ~write_n && (address == 1);
always @(posedge clk or negedge reset_n)
begin
if (reset_n == 0)
PWM_duty_reg<=0;
else if (PWM_duty_wr_strobe)
PWM_duty_reg<=writedata;
end

//operate PWM_enable_reg
assign PWM_enable_wr_strobe= chipselect && ~write_n && (address == 2);
always @(posedge clk or negedge reset_n)
begin
if (reset_n == 0)
PWM_enable<=0;
else if (PWM_enable_wr_strobe)
PWM_enable=writedata[0];
end
//achieve PWM function

//counter
assign PWM_control=PWM_enable;
always @(posedge clk or negedge reset_n)
begin
if (reset_n == 0)
PWM_counter<=0;
else
if(PWM_control)
if(PWM_counter<PWM_period_reg)
PWM_counter<=PWM_counter+32'd1;
else
PWM_counter<=0;
end


//PWM output
always @(posedge clk or negedge reset_n)
begin
if (reset_n == 0)
PWM_out_reg<=0;
else
if(PWM_control)
begin
if(PWM_counter<PWM_duty_reg)
PWM_out_reg<=1'b1;
else PWM_out_reg<=1'b0;
end
else PWM_out_reg<=1'b0;
end
assign PWM_out=PWM_out_reg;
endmodule

当然IP定制完成后是需要进行modelsim的仿真的。前面一篇博客发布了一点关于testbench的认识的原因。工欲善其事必先利其器,设计到了这步,还想靠quartus II自带的仿真工具来验证是很难的。

定制了nios II 软核后我们需要在nios II中自己编写HAL文件。

我得提出来为了对比效果我在自己的SOPC搭建的工程中不只使用了PWM的输出,还使用了一个两位宽的PIO。因为我是用LED来观察我得实验效果的,当然没有示波器那么专业。

在nios II中建立工程后,首先是HAL文件的设计,我在自己的PWM.h文件中声明了以下内容

#ifndef PWM_H_

#define PWM_H_



#include <io.h>

#define IO_WR_PWM_PERIOD(data) IOWR(PWM_BASE,0,data)

#define IO_WR_PWM_DUTY(data) IOWR(PWM_BASE,1,data)

#define PWM_enable IOWR(PWM_BASE,2,1)

#define PWM_disble IOWR(PWM_BASE,2,0)



void PWM_configuration(alt_32 period,alt_32 duty );

#endif /*PWM_H_*/

这里的#include <io.h>一定是要记得加进去的,否则无法编译。至于后面的一些一句的来历我不想再赘述。

然后是PWM.c文件。很简单的几行代码。

#include "system.h"

#include "alt_types.h"

#include "../inc/pwm.h"





void PWM_configuration(alt_32 period,alt_32 duty )

{

IO_WR_PWM_PERIOD(period);

IO_WR_PWM_DUTY(duty);

}


 

到此我们的设计很容易看懂了,接下来就是main.c了。

#include "system.h"
#include "alt_types.h"
#include "unistd.h"
#include "altera_avalon_pio_regs.h"
#include "../inc/pwm.h"

void delay(alt_u32 i);

int main (void) __attribute__ ((weak, alias ("alt_main")));

int alt_main()
{
PWM_configuration(50000000,10000000);//50 000 000,10 000 0000
PWM_enable;
while(1)
{
IOWR_ALTERA_AVALON_PIO_DATA(LED2_BASE, 0);
delay(500000);
IOWR_ALTERA_AVALON_PIO_DATA(LED2_BASE, 3);
delay(500000);
}
}



void delay(alt_u32 i)
{
for(;i>0;i--);
}

以上程序的PWM波形输出是按照1Hz,1/5占空比设计的,所以是可以看到LED闪烁的。

 编译下载,OK,可以看到明显的效果,三个LED闪烁起来,而且频率也是不一样的。因为手头没有示波器,我也没有用示波器来测量频率和占空比。
到此简单的IP设计思想和代码为止。
引用请声明来源:http://www.cnblogs.com/michile/admin/EditPosts.aspx   by  michile
原文地址:https://www.cnblogs.com/michile/p/2263086.html