Menu Close

7段数码管IP CPU 接口封装

在我们学习过7段数码管的fpga设计后, 掌握的数码管的显示原理, fpga的实现,以及系统数据在数码管上的如果显示等。 在完成数码管工程后, 我们完全可以形成一个完整的IP核,提供给以后的fpga 设计,或者是soc设计中的一个IP。而不是每次得到一个项目,都要重新编写相关的代码。 即使是硬件方面可能有所不同, 需要更改数码管IP 是,也变得比较简单。 一个中型,或者是大型的项目, 往往都是通过一些简单的IP 模块搭建起来的。当我们掌握了比较多的IP,对于一个中型,大型项目的设计是很有帮助的。

 

1.项目中的系统分层结构:

图1

在一个工程中, 首先,我们先实现一个功能的IP 核, 在对这个FPGA IP 使用, 测试后, 就可以对这个功能IP 进行封装。 封装的原则是:

  • 适用性强,可以比较容易的被当前的工程,或者其他FPGA 工程中反复使用,
  • 方便例化,在有些工程中,会连续例化很多个相同功能的IP。 举例:有些项目中会有8个 或者是16 个uart 模块。
  • 具有丰富的接口参数, 方便一个相同的IP, 适应于不同的配置模式,而不是为每一种功能,单独生成自己的IP。
  • 附加选项,适用于不同型号的FPGA 芯片。

在很多fpga 的项目中: 我们通常会有一个硬件平台(硬件开发板),在硬件平台是由 FPGA 芯片, cpu 芯片,时钟, 电源芯片等组成。在fpga设计中,

  • 有时需要使用fpga 来控制硬件平台中其他芯片的操作,或者核其他硬件平台通讯。( 以太网通讯, uart 通讯,iic通讯等)
  • fpga 本身会完成相应的算法,(加密,解密,图像压缩, dsp 算法)
  • 和上层的cpu 进行数据交换。

在一些中型,或者大型的项目中,往往是在硬件平台下, fpga, cpu 协同工作, 完成某些具体的功能。所以经常分成以下几类模块

  • FPGA 功能IP
  • FPGA 寄存器
  • CPU 总线接口
  • CPU 软件驱动
  • CPU 软件应用程序

 

FPGA 功能IP: 完成具体功能模块,行为。

FPGA 寄存器:提供IP 模块接口的参数配置, 读取IP模块的相关内容

CPU 总线接口:链接FPGA 寄存器 和 cpu 总线, 提供读信号,写信号,地址, 数据读,数据写等。

CPU 软件驱动:软件驱动用来配置FPGA 寄存器,通过对寄存器的操作, 实现软件对FPGA IP 的读写操作。

CPU 软件应用程序:调用软件驱动, 实现上层应用功能。

 

2.对FPGA IP 的封装:

这里我们以7段数码管为例, 实现IP核到软件应用的设计流程。

`timescale 1ns / 1ps 
//////////////////////////////////////////////////////////////////////////////////
// Company:     Fraser Innavotion Inc
// Engineer:    William Gou
//
// Create Date: 09/25/2018 09:28:18 AM
// Design Name:
// Module Name: SEG_core
// Project Name: RISC-V SOC RTL
// Target Devices: FII-PE7030, FII-PRX100T, FII-BM7100, FII-PRA040, FII-PRA006
// Tool Versions: vivado 18.1/18.2
// Description:
//
// Dependencies:
// this is a simple version of risc-v integer version,aim to show up basic concept of RTL
// Revision:1.0
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////

module SEG_core #
(
    parameter MS_CNT = 50_000
)
(
    input        clk,
    
    input  [7:0] seg_in_a,
    input  [7:0] seg_in_b,
    input  [7:0] seg_in_c,
    input  [7:0] seg_in_d,
    input  [7:0] seg_in_e,
    input  [7:0] seg_in_f,
    
    input  [5:0] dp_in,
    
    output [6:0] SEG,
    output       DP,
    output [5:0] SEAT,

    input        rst
);

reg [31:0] ms_cnt = 0;
always@(posedge clk or posedge rst )
if(rst) ms_cnt <= 0;
else 
begin
    if(ms_cnt == MS_CNT - 1) ms_cnt <= 0;
    else ms_cnt <= ms_cnt + 1;
end

wire ms_p = (ms_cnt == MS_CNT - 1) ? 1'b1 : 1'b0;

 
reg [7:0] count_sel;
reg seg_dp = 1; 
reg [5:0] seg_seat = 0;
always@(posedge clk or posedge rst )
if(rst) 
begin
    seg_seat <= 0;
    count_sel <= " ";
    seg_dp <= 1'b1;
end
else 
begin
    if(ms_p) 
    begin
        case (seg_seat)
        6'b10_0000:     
        begin
            seg_seat <= 6'b00_0001;
            count_sel <= seg_in_f;
            seg_dp <= dp_in[0];
        end
        6'b00_0001: 
        begin
            seg_seat <= 6'b00_0010;
            count_sel <= seg_in_e;
            seg_dp <= dp_in[1];
        end
        6'b00_0010: 
        begin
            seg_seat <= 6'b00_0100;
            count_sel <= seg_in_d;
            seg_dp <= dp_in[2];
        end
        6'b00_0100: 
        begin
            seg_seat <= 6'b00_1000;
            count_sel <= seg_in_c;
            seg_dp <= dp_in[3];
        end
        6'b00_1000: 
        begin
            seg_seat <= 6'b01_0000;
            count_sel <= seg_in_b;
            seg_dp <= dp_in[4];
        end
        6'b01_0000: 
        begin
            seg_seat <= 6'b10_0000;
            count_sel <= seg_in_a;
            seg_dp <= dp_in[5];
        end
        default: seg_seat <= 6'b10_0000;
        endcase
    end
end

// seg_seg = {a,b,c,d,e,f,g,dp}
/*   --- a ---
 *   |       |
 *   f       b
 *   |       |
 *   --- g ---
 *   |       |
 *   e       c
 *   |       |
 *   --- d ---   dp
 *
*/

reg [6:0] seg_seg = 7'h00; 
always@(*)
case(count_sel)
"F",
"f" : seg_seg = 7'b1000_111;
"r" : seg_seg = 7'b0000_101;
"A",
"a" : seg_seg = 7'b1110_111;
"S" : seg_seg = 7'b1011_011;
"E",
"e" : seg_seg = 7'b1001_111;
"i" : seg_seg = 7'b0000_100;
"I" : seg_seg = 7'b0000_110;
"H" : seg_seg = 7'b0110_111;
"h" : seg_seg = 7'b0010_111;
"U" : seg_seg = 7'b0111_110;
"u" : seg_seg = 7'b0011_100;
"b",
"B" : seg_seg = 7'b0011_111;
"d",
"D" : seg_seg = 7'b0111_101;
"o" : seg_seg = 7'b0011_101;
"P",
"p" : seg_seg = 7'b1100_111;
"L" : seg_seg = 7'b0001_110;
"n" : seg_seg = 7'b0010_101;
"N" : seg_seg = 7'b1110_110;
"C" : seg_seg = 7'b1001_110;
"c" : seg_seg = 7'b0001_101;
"g" : seg_seg = 7'b1111_011;
"j",
"J" : seg_seg = 7'b0111_000;
" " : seg_seg = 7'b0000_000;
"_" : seg_seg = 7'b0001_000;

"-" : seg_seg = 7'b0000_001;
"0" : seg_seg = 7'b1111_110;
"1" : seg_seg = 7'b0110_000;
"2" : seg_seg = 7'b1101_101;
"3" : seg_seg = 7'b1111_001;
"4" : seg_seg = 7'b0110_011;
"5" : seg_seg = 7'b1011_011;
"6" : seg_seg = 7'b1011_111;
"7" : seg_seg = 7'b1110_000;
"8" : seg_seg = 7'b1111_111;
"9" : seg_seg = 7'b1110_011;

default: seg_seg = 7'h00;
endcase


assign SEG  = ~{seg_seg[0], seg_seg[1], seg_seg[2], seg_seg[3], seg_seg[4], seg_seg[5], seg_seg[6] }; 
assign DP   = ~seg_dp;

assign SEAT = ~seg_seat;

endmodule

 

module SEG_core #
(
    parameter MS_CNT = 50_000
)
(
    input clk,

    input [7:0] seg_in_a,
    input [7:0] seg_in_b,
    input [7:0] seg_in_c,
    input [7:0] seg_in_d,
    input [7:0] seg_in_e,
    input [7:0] seg_in_f,

    input [5:0] dp_in,

    output [6:0] SEG,
    output DP,
    output [5:0] SEAT,

    input rst
);

MS_CNT: 用于设置 1ms 时钟,用户可以根据不同的时钟, 通过MS_CNT 来设置1ms 时钟。

seg_in_x : 用于用户数据输入接口

dp_in :      用于用户输入 7 段数码管的point

SEG:  7段数码管的输出

DP:   数码管 point 输出

SEAT: 数码管位置pin 输出

3.寄存器接口设计:

根据不同的应用,可以设计出不同的寄存器接口。

寄存器地址 寄存器描述 寄存器初值 读写能力
16’h0000 模块版本寄存器 32’h0000_0001 只读
16’h0004 模块控制寄存器 32’h0000_0000 读写
16’h001c 数据1寄存器 32’h0000_0000 读写
16’h0020 数据2寄存器 32’h0000_0000 读写

 

wire [ 7: 0 ] w_addr = i_addr[ 9: 2 ];

//===============================================================================
wire [ 7: 0 ] SEG_VER = 8'h01;
reg  [31: 0 ] SEG_CTRL = 0;
reg  [31: 0 ] SEG_DATA0 = 0;
reg  [31: 0 ] SEG_DATA1 = 0;


always @( posedge clk )
if(!rst_n) SEG_CTRL <= 0;
else 
begin
    SEG_CTRL[30] <= 1'b0; // update bit
    if(w_addr == 1)  // register address
    begin
        if( we_i[0]) SEG_CTRL[07:00] <= i_PERI_din[07:00];
        if( we_i[1]) SEG_CTRL[15:08] <= i_PERI_din[15:08];
        if( we_i[2]) SEG_CTRL[23:16] <= i_PERI_din[23:16];
        if( we_i[3]) SEG_CTRL[31:24] <= i_PERI_din[31:24];
    end
end

always @( posedge clk )
if(!rst_n) SEG_DATA0 <= 0;
else if(w_addr == 7)  // register address
begin
    if( we_i[0]) SEG_DATA0[07:00] <= i_PERI_din[07:00];
    if( we_i[1]) SEG_DATA0[15:08] <= i_PERI_din[15:08];
    if( we_i[2]) SEG_DATA0[23:16] <= i_PERI_din[23:16];
    if( we_i[3]) SEG_DATA0[31:24] <= i_PERI_din[31:24];
end

always @( posedge clk )
if(!rst_n) SEG_DATA1 <= 0;
else if(w_addr == 8)  // register address
begin
    if( we_i[0]) SEG_DATA1[07:00] <= i_PERI_din[07:00];
    if( we_i[1]) SEG_DATA1[15:08] <= i_PERI_din[15:08];
    if( we_i[2]) SEG_DATA1[23:16] <= i_PERI_din[23:16];
    if( we_i[3]) SEG_DATA1[31:24] <= i_PERI_din[31:24];
end
//===============================================================================
reg [7:0] SEG_VAL [5:0];
reg [5:0] SEG_DP = 0;
initial
begin
    SEG_VAL[0] = " ";
    SEG_VAL[1] = " ";
    SEG_VAL[2] = " ";
    SEG_VAL[3] = " ";
    SEG_VAL[4] = " ";
    SEG_VAL[5] = " ";
    
    SEG_DP = 0;
end

always @ (posedge clk)
if(SEG_CTRL[30]) 
begin
    SEG_VAL[0] <= SEG_DATA0[07:00];
    SEG_VAL[1] <= SEG_DATA0[15:08];
    SEG_VAL[2] <= SEG_DATA0[23:16];
    SEG_VAL[3] <= SEG_DATA0[31:24];
    SEG_VAL[4] <= SEG_DATA1[07:00];
    SEG_VAL[5] <= SEG_DATA1[15:08];
    
    SEG_DP <= SEG_CTRL[5:0];
end

//=======   ======================bus read=======================================
always @ (*)
begin
    case (w_addr[7:0])
    8'h00: o_PERI_dout = {24'h00_0000, SEG_VER};
    8'h01: o_PERI_dout = SEG_CTRL;
    8'h02: o_PERI_dout = SEG_STAT;
    8'h07: o_PERI_dout = SEG_DATA0;
    8'h08: o_PERI_dout = SEG_DATA1;
    default: o_PERI_dout = 0;
    endcase
end

 

模块版本寄存器(00h): 被cpu 读取, 显示当前模块的版本

模块控制寄存器(04h):模块控制寄存器,

bit[30] , 1 => update 7段数码管值, 当cpu 写入后, fpga 端自动清零

bit[5:0]  表示7段数码管小数点的位置。

数据1寄存器 (1ch):7段数码管值

数据2寄存器(20h):7段数码管值

4.CPU 接口设计:

module RIB_fii_SEG
#(
    parameter [ 31: 0 ] DEV_BASEADDR   = 32'hC000_1000,

    parameter PERI_DEEP     = 4,    //memory data width
    parameter PERI_W        = 32,   //memory data width
    parameter PERI_MSK_W    = 4,    //memory data mask width
    parameter PERI_ADDR_W   = 32    //memory address width
)
(
    input  clk,
    
    input  [31:0] i_rib_saddr,
    input  [31:0] i_rib_sdin,
    output [31:0] o_rib_sdout,
    input  i_rib_svalid,
    output [1:0] o_rib_sready,
    input  [3:0]  i_rib_swe,
    input  i_rib_srd,
    input  i_rib_sop,
    
    output [6:0] SEG,
    output       DP,
    output [5:0] SEAT,

    input  rst_n
);
    
wire [ PERI_W - 1 : 0 ] i_PERI_din = i_rib_sdin;
reg [ PERI_W - 1 : 0 ] o_PERI_dout;
assign    o_rib_sdout = o_PERI_dout << {i_rib_saddr[1:0],3'b000};

wire  [ PERI_ADDR_W - 1 : 0 ] i_addr = i_rib_saddr;

wire  cs_i = ( i_rib_saddr[31:12] == DEV_BASEADDR[31:12] ) ? 1'b1 : 1'b0;
//===============================================================================
wire [ PERI_MSK_W - 1: 0 ] we_i = ( { PERI_MSK_W{ cs_i } } & i_rib_swe );
wire rd_i = cs_i & i_rib_srd;

wire [ 7: 0 ] w_addr = i_addr[ 9: 2 ];

assign o_rib_sready = {1'b0, i_rib_svalid & cs_i};
//===============================================================================

 

RISCV  RIB 总线:

input  [31:0] i_rib_saddr,
input  [31:0] i_rib_sdin,
output [31:0] o_rib_sdout,
input  i_rib_svalid,
output [1:0] o_rib_sready,
input  [3:0] i_rib_swe,
input  i_rib_srd,
input  i_rib_sop,

RISCV  RIB 总线 转换为 地址, 读, 写,等信号:

wire [ PERI_W - 1 : 0 ] i_PERI_din = i_rib_sdin;
reg [ PERI_W - 1 : 0 ] o_PERI_dout;
assign o_rib_sdout = o_PERI_dout << {i_rib_saddr[1:0],3'b000};

wire [ PERI_ADDR_W - 1 : 0 ] i_addr = i_rib_saddr;

wire cs_i = ( i_rib_saddr[31:12] == DEV_BASEADDR[31:12] ) ? 1'b1 : 1'b0;
//===============================================================================
wire [ PERI_MSK_W - 1: 0 ] we_i = ( { PERI_MSK_W{ cs_i } } & i_rib_swe );
wire rd_i = cs_i & i_rib_srd;

wire [ 7: 0 ] w_addr = i_addr[ 9: 2 ];
assign o_rib_sready = {1'b0, i_rib_svalid & cs_i};
//===============================================================================

经过一些简单的逻辑转换, 实现总线到读写逻辑的转换。

5.软件驱动:

#define  SEG_REG(offset)    (*(volatile unsigned int *)(0xC0001000 + offset))

SEG_REG(0x1c) = 0x01234567;
SEG_REG(0x20) = 0x89abcdef;

SEG_REG(0x04) = 0xc0000008;

驱动程序先将7段数码管的数值写入到寄存器中, 最后写控制寄存器,控制寄存器包括小数点的位置,和bit【30】 update 信息。

6.软件应用程序:

/*Entry Point for Machine Timer Interrupt Handler*/
void handle_m_time_interrupt(){
    clear_csr(mie, MIP_MTIP);  // clear MIP_MTIP bit

    // Reset the timer for 3s in the future.
    // This also clears the existing timer interrupt.
    set_timer(1); // reset the timer for 250ms

    // read the current value of the LEDS and invert them.
    if((time_cnt % 500 ) == 0) // 1s
    {
        u8_t led_reg = 0;
        led_reg = (time_cnt >> 2) & 0x01;
        led_reg = led_reg << 6;
        led_reg |= (ETH_REG(ETH_STAT) & 0xff);
        GPIO_REG(LED_VAL) = ~(led_reg);
//        printf("time_cnt = 0x%08x \r\n", time_cnt);
    }
#ifdef SEG_IP
    if((time_cnt % 0x400 ) == 0) // 1s
    {
        if(time_cnt & 0x800)
        {
            SEG_REG(0x1c) = *((u32_t *)(&seg_ip[0]));
            SEG_REG(0x20) = *((u32_t *)(&seg_ip[4]));
        }
        else
        {
            SEG_REG(0x1c) = *((u32_t *)(&seg_ip[6]));
            SEG_REG(0x20) = *((u32_t *)(&seg_ip[10]));
        }

        SEG_REG(0x04) = 0xc0000008;
    }
#endif
    time_cnt ++;
    // Re-enable the timer interrupt.
    set_csr(mie, MIP_MTIP);  // set MIP_MTIP bit
}

 

上层的应用程序handle_m_time_interrupt() 调用底层的驱动程序。

 

到此,实现了底层的verilog IP 开发,到上层软件应用程序的调用过程。

Posted in FPGA, FPGA 教材教案, Verilog, 教材与教案, 文章

发表回复

相关链接