在我们学习过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 开发,到上层软件应用程序的调用过程。