在我们学习过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 寄存器:提供IP 模块接口的参数配置, 读取IP模块的相关内容
CPU 总线接口:链接FPGA 寄存器 和 cpu 总线, 提供读信号,写信号,地址, 数据读,数据写等。
CPU 软件驱动:软件驱动用来配置FPGA 寄存器,通过对寄存器的操作, 实现软件对FPGA IP 的读写操作。
CPU 软件应用程序:调用软件驱动, 实现上层应用功能。
2.对FPGA IP 的封装:
这里我们以7段数码管为例, 实现IP核到软件应用的设计流程。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
`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 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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 | 读写 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
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 接口设计:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
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 总线:
|
1 2 3 4 5 6 7 8 |
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 总线 转换为 地址, 读, 写,等信号:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
wire [ PERI_W - 1 : 0 ] <span style="color: #0000ff;">i_PERI_din</span> = i_rib_sdin; reg [ PERI_W - 1 : 0 ] <span style="color: #0000ff;">o_PERI_dout</span>; assign o_rib_sdout = o_PERI_dout << {i_rib_saddr[1:0],3'b000}; wire [ PERI_ADDR_W - 1 : 0 ] <span style="color: #0000ff;">i_addr</span> = i_rib_saddr; wire <span style="color: #0000ff;">cs_i</span> = ( i_rib_saddr[31:12] == DEV_BASEADDR[31:12] ) ? 1'b1 : 1'b0; //=============================================================================== wire [ PERI_MSK_W - 1: 0 ] <span style="color: #0000ff;">we_i</span> = ( { PERI_MSK_W{ cs_i } } & i_rib_swe ); wire <span style="color: #0000ff;">rd_i</span> = 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.软件驱动:
|
1 2 3 4 5 6 |
#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.软件应用程序:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/*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 开发,到上层软件应用程序的调用过程。