这篇笔记是我之前在调试MicroBlaze时记录下来的,当时在网上查了一些资料,发现都讲的不是特别清楚,所以自己整理了一个笔记,如有差错,希望大家指正。

在这次示例中,本文完成了一个改变流水灯的间隔时间以及按键检测的间隔时间可变的一个MicroBlaze程序,修改参数后不用再经过布局布线,方便调试。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

 

典型的嵌入式程序设计流程如下:

EDK笔记——自定义IP核 随笔 第1张

如上图所示,在FPGA中开发嵌入式系统主要需要三个工具套件,分别为ISE,XPS和SDK。

 

ISE篇

先打开ISE,新建一个Project

EDK笔记——自定义IP核 随笔 第2张

我用的是黑金的AX309的板子

EDK笔记——自定义IP核 随笔 第3张

EDK笔记——自定义IP核 随笔 第4张

然后建一个Embedded Processor

EDK笔记——自定义IP核 随笔 第5张

 

XPS篇

建完后会自动打开XPS软件,点击Yes创建一个新的XPS Project

EDK笔记——自定义IP核 随笔 第6张

点击OK,使用AXI总线

EDK笔记——自定义IP核 随笔 第7张

选择Create a System for a Custom Board,  Reference Clock Frequecy是板上的时钟频率,也可以是你用PLL生成的时钟然后输到MIcroBlaze系统的时钟频率,Optimization Strategy选择Area和Throughput都可以,在这里我选的是Area。点击Next

EDK笔记——自定义IP核 随笔 第8张

处理器频率选择100Mhz,Local Memory Size选择32KB,用于缓存你写的应用程序,尽量选大一些。不用添加其它外设,点击Finish即可

EDK笔记——自定义IP核 随笔 第9张

完了之后会生成一个基本的MicroBlaze系统,里面包含了MIcroBlaze系统所需的基本模块,如clock generator,axi4liite等等。

EDK笔记——自定义IP核 随笔 第10张

在这里,我们需要自己新建一个IP,在建IP之前,我们要先想好这个IP的功能和端口是什么,包括输入和输出端口,我目前要实现的功能是,通过检测按键KEY1,来开启和关闭流水灯,同时可以通过修改MIcroBlaze中的程序来改变流水灯的间隔时间以及按键检测的间隔时间,所以在这个自定义IP中,端口有两个,一个是控制4个LED灯亮的输出端口LED[3:0],一个是按键的输入端口dip。

 

首先,点击Hardware>>Create or Import Peripheral

 EDK笔记——自定义IP核 随笔 第11张

 

点击next

EDK笔记——自定义IP核 随笔 第12张

 

选择Create templates for a new perphera,点击next

 

EDK笔记——自定义IP核 随笔 第13张

 

在这里有两个选择,上面那个是将你创建的外设安装到EDK的库中,这样下次再创建工程就可以直接利用你创建的外设,可重复利用,另外一个选项就是只用于当前XPS Project,在这里我们这个外设只在这个工程中使用,所以选择To an XPS Project,点击next。

EDK笔记——自定义IP核 随笔 第14张

 

为你创建的这个外设取名字,我这里取的是blink,你可以在description中添加对这个外设功能的描述。点击next

EDK笔记——自定义IP核 随笔 第15张

 

选择外设总线相连的协议,在这里选的是AXI4_Lite,点击next。

EDK笔记——自定义IP核 随笔 第16张

 

在这里需要勾选User logic software registers,表示我们是通过软件来更改寄存器的值,从而来更改我们所需要改变的参数的值。点击next

EDK笔记——自定义IP核 随笔 第17张

 

在这里我们选择寄存器的个数,一般有几个参数就选择几个寄存器,也可以把几个参数合在一个寄存器里,每个寄存器是32位的,总共可以选择32个寄存器,在这里我们需要修改两个参数(流水灯亮的间隔时间,按键检测的间隔时间),所以这里选择两个寄存器。

EDK笔记——自定义IP核 随笔 第18张

 

在这里不作修改,点击next

EDK笔记——自定义IP核 随笔 第19张

 

这里是选择是否生成仿真平台和仿真模型(Simulation Model),用于在ISim或Modelsim中进行仿真,在这里我们不需要仿真,不勾选,点击next

EDK笔记——自定义IP核 随笔 第20张

 

因为我们是用Verilog进行编程,所以需要勾选Generate stub“user logic” template in Verilog instead of VHDL,之后我们再对user logic模块进行修改时就可以用Verilog了,第三个勾选的话可以生成相关驱动,这样便可以利用驱动中的API函数了,不过不勾选也没关系,因为我们需要的函数在MIcroBlaze核里都有,在这里我们不勾选,点击next

EDK笔记——自定义IP核 随笔 第21张

EDK笔记——自定义IP核 随笔 第22张

 

 点击finish

EDK笔记——自定义IP核 随笔 第23张

 

这个时候可以看到,在IP Catalog中的USER有我们创建的外设BLINK

EDK笔记——自定义IP核 随笔 第24张

 

在这里我们需要修改几个文件,首先右键BLINK>>view MPD,在## Ports处添加外设的端口LED[3:0]和dip

EDK笔记——自定义IP核 随笔 第25张

 

接下来是VHDL文件和Verilog文件,这两个文件在 工程路径\\ pcores\blink_v1_00_a\hdl文件夹中,我的路径是H:\project\EDK_test\system_test\pcores\blink_v1_00_a\hdl,   先修改VHDL文件,点击VHDL文件夹,打开blink,vhd。有三个地方修改,一个是entity这里需要添加端口 EDK笔记——自定义IP核 随笔 第26张

 

一个是user logic这里需要添加端口

EDK笔记——自定义IP核 随笔 第27张

 

最后一个就是generic map这里需要添加映射

EDK笔记——自定义IP核 随笔 第28张

这样VHDL文件便修改完了,然后再修改verilog文件   Verilog文件主要就是你要实现的功能的代码实现,有关AXI4相关的通信方面的连线及端口已经写好了,我们要做的是写自己的逻辑。   同样,我们user logic中也要先添加自己的端口LED和dip EDK笔记——自定义IP核 随笔 第29张

EDK笔记——自定义IP核 随笔 第30张

 

通过软件控制的寄存器便是这两个寄存器

EDK笔记——自定义IP核 随笔 第31张

  上图是有关写寄存器的操作,这里不需要更改,软件赋予寄存器什么值,相应的寄存器就会是什么值。   下图是有关寄存器读的一些操作,在这里我们可以修改,这样软件便可以读到我们想要的信号的值,比如下图中寄存器2写操作写的是设定按键检测的时间间隔,读操作的时候读的是dip也就是开关的状态 EDK笔记——自定义IP核 随笔 第32张

 

接下来就是自己为实现功能而写的一些模块,以下是为了实现功能而编写的Verilog文件

EDK笔记——自定义IP核 随笔 第33张
//User Logic
//reg0    流水灯时间间隔
//reg1    按键检测时间间隔,用于防抖
//reg1    读入的时候读的是dip的状态
reg [31:0]  timer;
reg timer_enable;
always @ (posedge Bus2IP_Clk)
begin
  if (Bus2IP_Resetn == 1'b0) begin
    timer <= 32'b0;
  end else if (timer == 4*slv_reg0) begin
    timer <= 32'b0;
  end else if (timer_enable) begin
    timer <= timer + 1'b1;
  end else begin
    timer <= 32'b0;
  end
end


//按键间隔时间计数
reg [31:0] counter;
always @ (posedge Bus2IP_Clk)
begin
  if (Bus2IP_Resetn == 1'b0) begin
    counter <=32'b0;
  end else if (counter == slv_reg1) begin
    counter <= 32'b0;
  end else begin
    counter <= counter + 1'b1;
  end
end


//检测按键的下降沿
reg dip_temp1;
reg dip_temp2;
wire dip_neg;
always @ (posedge Bus2IP_Clk)
begin
  if (Bus2IP_Resetn == 1'b0) begin
    dip_temp1 <= 1'b1;
  end else if (counter == slv_reg1) begin
    dip_temp1 <= dip;
  end else begin
    dip_temp1 <= dip_temp1;
  end
end

//锁定检测dip的值
always @ (posedge Bus2IP_Clk)
begin
  if (Bus2IP_Resetn == 1'b0) begin
    dip_temp2 <= 1'b1;
  end else begin
    dip_temp2 <= dip_temp1;
  end
end

assign dip_neg = dip_temp2 & (~dip_temp1);


//流水灯的主要状态机
reg [3:0] current_state;
reg [3:0] next_state;

parameter State_idle = 4'd0;
parameter First_LED = 4'd1;
parameter Second_LED = 4'd2;
parameter Third_LED = 4'd3;
parameter Last_LED = 4'd4;

always @ (posedge Bus2IP_Clk)
begin
  if (Bus2IP_Resetn == 1'b0) begin
    current_state <= State_idle;
  end else begin
    current_state <= next_state;
  end
end

always @ (current_state,dip_neg,timer)
begin
  next_state = 4'dx;
  case (current_state)
    State_idle:
      begin
        if (dip_neg)     next_state = First_LED;
        else             next_state = State_idle;
      end
    First_LED:
      begin
        if (dip_neg)     next_state = State_idle;
        else if (timer == slv_reg0 - 1)   next_state = Second_LED;
        else                              next_state = First_LED;
      end
    Second_LED:
      begin
        if (dip_neg)     next_state = State_idle;
        else if (timer == 2*slv_reg0 - 1)   next_state = Third_LED;
        else                              next_state = Second_LED;
      end
    Third_LED:
      begin
        if (dip_neg)     next_state = State_idle;
        else if (timer == 3*slv_reg0 - 1)   next_state = Last_LED;
        else                              next_state = Third_LED;
      end
    Last_LED:
      begin
        if (dip_neg)     next_state = State_idle;
        else if (timer == 4*slv_reg0 - 1)   next_state = First_LED;
        else                              next_state = Last_LED;
      end
     default:
        next_state = State_idle;
    endcase
end


always @ (posedge Bus2IP_Clk)
begin
  if (Bus2IP_Resetn == 1'b0) begin
    timer_enable <= 1'b0;
    LED <= 4'b0000;
  end else begin
    case (next_state)
      State_idle:
        begin
          timer_enable <= 1'b0;
          LED <= 4'b0000;
        end
      First_LED:
        begin
          timer_enable <= 1'b1;
          LED <= 4'b0001;
        end
      Second_LED:
        begin
          timer_enable <= 1'b1;
          LED <= 4'b0010;
        end
      Third_LED:
        begin
          timer_enable <= 1'b1;
          LED <= 4'b0100;
        end
      Last_LED:
        begin
          timer_enable <= 1'b1;
          LED <= 4'b1000;
        end
        default:
            begin
             timer_enable <= 1'b0;
             LED <= 4'b0000;
            end
    endcase
  end
end
View Code

 

可以看到,流水灯的时间间隔是受slv_reg0控制,按键检测的时间间隔受slv_reg1控制。   修改完成后, 记得一定要点击Project->Rescan User Repositories,这样修改才能生效 EDK笔记——自定义IP核 随笔 第35张

 

然后再添加自己创建的外设BLINK,添加进来后,点击PORT栏,将LED和dip设为External Port,如下图所示

EDK笔记——自定义IP核 随笔 第36张

 

完成上述修改后,先在XPS中点击Hardware>>Generate Netlist,然后返回到ISE中,新建一个V文件,用作顶层文件,来例化这个MIcroBlaze核

EDK笔记——自定义IP核 随笔 第37张

 

 然后,编写UCF文件,将端口信号与管脚一一对应起来

EDK笔记——自定义IP核 随笔 第38张

 

最后,在ISE中点击生成Bit文件   生成完毕后,可以在ISE中先选中MIcroBlaze核再点击 Export Hardware Design To SDK with Bitsream,从而来打开SDK,如下图所示

EDK笔记——自定义IP核 随笔 第39张

 

也可以在XPS中点击 Hardware>> Export Hardware Design To SDK,来打开SDK,区别不同就在于前者打开SDK后,可以在SDK直接烧写BIt文件到FPGA中,然后用SDK进行调试,后者是先需要在ISE中烧写Bit文件,然后再打开SDK进行调试。

 

SDK篇 打开SDK,选择一个路径作为workspace后,然后新建一个Application Project,点击File>>New>>Appication Project, EDK笔记——自定义IP核 随笔 第40张

 

点击Finish,在生成的helloworld.c文件中进行程序编写

EDK笔记——自定义IP核 随笔 第41张

 

在这里,我先额外建了一个文件夹blink_led,放置写寄存器的操作

EDK笔记——自定义IP核 随笔 第42张
/*
 * blink_led.c
 *
 *  Created on: 2018-5-23

 */

#include "blink_led.h"
#include "xio.h"


//流水灯的间隔时间
void set_LED_interval(uint32_t  bassaddr_p, uint32_t num_ms, uint32_t num_us)
{
    //CLK 100MHZ
    XIo_Out32((bassaddr_p) + 0x00000000, num_ms*100000 + num_us*100);
}

//按键检测的间隔时间
void set_dip_interval(uint32_t bassaddr_p, uint32_t num_ms)
{
    //CLK 100MHZ
    XIo_Out32((bassaddr_p + 0x00000004), num_ms*100000);
}
View Code

其中寄存器的读写是靠xio.h中包含的API函数来进行操作的,所以要添加include“xio.h”,另外,此程序需要在helloword.c引用,所以也需要编写一个头文件,在helloword.c中进行程序的编写时需要包含进去

EDK笔记——自定义IP核 随笔 第44张
/*
 * blink_led.h
 *
 *  Created on: 2018-5-23
 */

#ifndef BLINK_LED_H_
#define BLINK_LED_H_

#include <stdint.h>

void set_LED_interval(uint32_t  bassaddr_p, uint32_t num_ms, uint32_t num_us);
void set_dip_interval(uint32_t bassaddr_p, uint32_t num_ms);

#endif /* BLINK_LED_H_ */
View Code

 

然后再在helloword.c中进行程序的编写

EDK笔记——自定义IP核 随笔 第46张
 */

#include <stdio.h>
#include "platform.h"
#include "blink_led/blink_led.h"
#include "xil_printf.h"

#define blink_LED_ADDR         0x7C600000


int main()
{
    init_platform();

    set_LED_interval(blink_LED_ADDR, 5000, 0);
    print("set_LED_interval Completed\n");
    set_dip_interval(blink_LED_ADDR, 10);
    print("set_dip_interval Completed\n");

    return 0;
}
View Code

 

在Debug之前,先设置Debug Configuration,勾上Connect STDIO to Console,这样,打印出来的信息就会显示在Console栏中。

EDK笔记——自定义IP核 随笔 第48张

至此整个自定义IP过程完成,接下来就是上电,将程序烧写进去进行调试就可以了。

 

 
扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄