现在演示如何在模板工程 DE10_NANO_SoC_FB 中设计一个用户自定义IP,并把这个IP 添加到HPS 系统当中,然后为该IP 编写Linux驱动,然后通过Linux应用程序来访问该IP。

之前发布过在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 添加到DE10_NANO_SoC_FB 的HPS系统中。

双击pwm,然后点击Finish就给HPS系统添加了一个名为pwm_0的IP 模块:

(1) 右击pwm_0选择Edit...将模块名称改为pwm;

(2)连接pwm模块的clock信号到clk_0模块的clk信号端;

(3)连接pwm模块的reset信号到clk_0模块的clk_reset信号端;

(4)连接pwm模块的as_slave信号到mm_bridge_0模块的m0信号端;

(5)双击Export栏的Double-click导出端口,并重命名为led;

(6)修改pwm 模块相对于h2f_lw_axi_master总线的及地址0x0000_6000(pwm 模块挂在h2f_lw_axi_master总线)。

 参考DE10_NANO_SoC_FB 的顶层文件DE10_NANO_SoC_FB.v可知 led_pio模块是绑定到FPGA 端的LED[7:1],LED[0] 也被占用,所以这里我们可以先删掉led_pio模块,具体做法是取消掉Platfom Designer 窗口led_pio模块前面的复选框即可:

然后 点击Generate HDL...——Generate——Close——Close——Finish:(这个过程会比较久)

 点击OK:

 点开Project——Add/Remove Files in Project...

 检查soc_system.qip文件已经存在(模板工程之前已经是添加过了的):

在顶层文件DE10_NANO_SoC_FB.v做如下修改并保存:

 (1)设置stm_hw_events信号位宽为[20:0]

(2)注释掉语句 assign LED[7:1] = fpga_led_internal;

 (3)在语句assign stm_hw_events    = {{15{1'b0}}, SW, fpga_debounced_buttons};中去掉fpga_led_internal

(4)打开DE10_NANO_SoC_FB工程的soc_system文件夹下的soc_system_inst.v文件找到新增的led_writeresponsevalid_n 端口并复制:

 (6)增加语句  .led_wire    ( LED[1] ),

(7)注销掉语句.led_pio_external_connection_export    ( fpga_led_internal ), 

 

保存完DE10_NANO_SoC_FB.v,点击如下按钮进行工程全编译:

 成功生成sof文件: