学习了很长时间的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闪烁的。