本文跟之前的教程《Platform Designer 自定义IP(用于纯RTL设计)-CSDN博客》 不一样,这里将Verilog模块封装成Avalon MM接口的的IP,用于Nios II/Nios V/HPS等系统控制。
之前发布过在DE10-Nano上实现PWM呼吸灯的设计,当时这个模块框图如下:
该模块内部有三个计数器,并且pwm波的频率和占空比都是电路里面设定好的。现在把这个pwm稍作修改,让pwm波的频率值和占空比值从模块外传递进来:
3.点击File——New——Verilog HDL File新建一个pwm.v文件并存放在工程目录下的ip/pwm文件夹下(文件夹自己新建)。修改代码如下:
module pwm(
input clk,
input rst_n,
input [31:0] cnt_freq,
input [31:0] cnt_duty,
output reg led);
reg [31:0] cnt0;
wire end_cnt0;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else begin
if(end_cnt0)begin
cnt0 <= 0;end
else begin
cnt0 <= cnt0 + 1;
end
end
end
assign end_cnt0 = (cnt0==cnt_freq -1);
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
led <= 1;
end
else if(cnt0==cnt_duty-1)begin
led <= 0;
end
else if(end_cnt0)begin
led <= 1;
end
end
endmodule
然后再将该模块封装成带有 Avalon-MM 接口的组件:
它有:
一个clock接口(clk_50),用来同步寄存器的值;
一个reset接口(rst_n),用来复位寄存器的值;
一组 slave 接口(as_read、as_address、as_write、as_writedata、as_readdata),用来读写寄存器;
一个 conduit 接口,用来与顶层LED的引脚进行连接。
该模块内设计了两个寄存器:
cnt_freq寄存器地址是0,用于存储pwm波的频率值;
cnt_duty寄存器地址是1,用于存储pwm波占空比值。
写寄存器
当as_chipselect 信号和 as_write 信号有效且 as_address地址是 0,将 as_writedata 端口上的值存入 cnt_freq寄存器;
当as_chipselect 信号和 as_write 信号有效且 as_address地址是 1,将 as_writedata 端口上的值存入 cnt_duty寄存器;
读寄存器
当as_chipselect 信号有效且as_address地址是 0,将cnt_freq寄存器的值赋给 readdata 端口。
当as_chipselect 信号有效且as_address地址是 1,将cnt_duty寄存器的值赋给 readdata 端口。
av_pwm.v 代码
module av_pwm(
input clk,
input reset_n,
input as_read,
input as_address,
input as_write,
output reg [31:0] as_readdata,
input [31:0] as_writedata,
output led);
reg [31:0]cnt_freq;
reg [31:0]cnt_duty;
pwm u_pwm(
.clk(clk),
.rst_n(reset_n),
.cnt_freq(cnt_freq),
.cnt_duty(cnt_duty),
.led(led)
);
//写pwm频率寄存器
always@(posedge clk or negedge reset_n)
if(!reset_n) begin
cnt_freq <= 32'd0;
end
else if(as_write && (as_address == 0))begin
cnt_freq <= as_writedata;
end
else begin
cnt_freq <= cnt_freq;
end
//写pwm占空比寄存器
always@(posedge clk or negedge reset_n)
if(!reset_n) begin
cnt_duty <= 32'd0;
end
else if(as_write && (as_address == 1)) begin
cnt_duty <= as_writedata;
end
else begin
cnt_duty <= cnt_duty;
end
//读寄存器
always@(posedge clk or negedge reset_n)
if(!reset_n)
as_readdata <= 32'd0;
else if(as_read)begin
case(as_address)
0:as_readdata <= cnt_freq;
1:as_readdata <= cnt_duty;
default:as_readdata <= 32'd0;
endcase
end
endmodule
封装PWM IP
点击Platform Designer 图标打开soc_system.qsys文件(如果提示IP 更新请直接点击Close关闭窗口,暂时不用更新IP):
点击File——New Component,依次填入如下信息:
然后点击Files 选项卡,点击Add File... ,将av_pwm.v 和pwm.v 两个文件添加进去:
这时候Component Editor 会自动识别顶层文件如下:
如果自动识别错误也可以手动设置,即双击对应文件的Attributes一栏,然后选中Top-level File 复选框,然后点击OK 即可。(注意,本案例要设置av_pwm.v文件为顶层文件)
文件添加完成后点击 Analyze Synthesis Files 来对添加的文件进行分析和综合(目的是检查源文件pwm.v和av_pwm.v中是否有语法问题)。
分析综合如果出现语法错误会有Analyzing Synthesis Files Completed窗口提示:
用户需要根据提示将问题一一改正,然后再次点击 Analyze Synthesis Files 直到Analyzing Synthesis Files Completed窗口不再报错以后,点击Close:
当然,可能此时Message窗口还有这些错误提示,暂时不管。( 注意:不要把 message栏中的错误提示误认为是综合分析的错误提示了, message 栏中的错误提示是告诉用户这个组件的某些设置有问题,这些是需要在接下来的步骤中解决的。)
Component Editor 在分析综合源文件后会根据组件端口的信号名进行预分组,这些可能不太正确,需要用户手动调整。用户可在Signals & Interfaces页面中对组件的接口和信号进行手动调整(比如进行端口的分组,信号的编辑、删除、新增)。
设置时钟信号,选中左侧的clk[1]信号,在右侧信号参数窗口修改设置如下:
设置复位信号,选中左侧的reset_n[1]信号,在右侧信号参数窗口修改设置如下:
点击左侧的as,将右侧的Name改为as_slave,Associated Clock设置为clock,Associated Reset设置为reset。(注:完成这一步骤的设置后,下方的Messages中的Error数量由5个变为4个。)
点击左侧的avs_address[1]信号,在右侧信号参数窗口修改设置如下:
点击左侧的avs_read[1]信号,在右侧信号参数窗口修改设置如下:
点击左侧的avs_readdata[32],在右侧信号参数窗口修改设置如下:
点击左侧的avs_write[1],在右侧信号参数窗口修改设置如下:
点击左侧的avs_writedata[8],在右侧信号参数窗口修改设置如下:
点击左侧最下面<<add interface>>选择conduit:
点击端口类型conduit_end,在右侧信号参数窗口修改设置如下:
然后左击选中led[1]信号,左击不放,拖拽led[1]信号到端口类型conduit_end下面,并确保右侧信号参数窗口设置如下(应该是output, 这里的图后期会改):
左击选中端口类型avalon_slave_0,继续右击该端口类型并选择菜单Remove 移除该多余的端口类型:
至此,已经完成了pwm IP的所有设置,点击Block Symbol 选项卡,可以看到完整的端口信息:
点击最下方的Finish...按钮保存(点击Finish之前要再次检查下as_slave的Associated Reset是reset而不是none),此时会弹出Save Changes的提示框,点击Yes, Save进行保存。
保存完成后,Component Editor工具自动退出,返回Platform Designer界面。此时,在IP Catalog下便可以看到我们创建的pwm IP:
接下来将pwm IP 添加到Platform Designer系统中。