Menu Close

7-segment digital tube IP CPU interface package

After we have studied the fpga design of the 7-segment nixie tube, we have mastered the display principle of the nixie tube, the realization of the fpga, and the display of the system data on the nixie tube, etc. After the completion of the digital tube project, we can form a complete IP core, which can be provided to the future fpga design, or an IP in the soc design. Instead of rewriting the relevant code every time you get a project. Even if the hardware may be different, it becomes easier to change the IP of the nixie tube. A medium-sized or large-scale project is usually built through some simple IP modules. When we have more IP, it is very helpful for the design of a medium-sized or large-scale project.

 

1. System hierarchy in the project:

figure 1

In a project, first, we first implement a functional IP core, and after using and testing this FPGA IP, we can encapsulate this functional IP. The principles of encapsulation are:

  • It has strong applicability and can be easily used repeatedly in current projects or other FPGA projects.
  • It is convenient for instantiation. In some projects, many IPs with the same function will be instantiated continuously. Example: Some projects will have 8 or 16 uart modules.
  • With rich interface parameters, it is convenient for the same IP to adapt to different configuration modes, instead of generating its own IP for each function.
  • Additional options for different models of FPGA chips.

In many FPGA projects: We usually have a hardware platform (hardware development board), the hardware platform is composed of FPGA chip, cpu chip, clock, power chip and so on. In fpga design,

  • Sometimes it is necessary to use fpga to control the operation of other chips in the hardware platform, or to communicate with other hardware platforms. (Ethernet communication, uart communication, iic communication, etc.)
  • The fpga itself will complete the corresponding algorithm, (encryption, decryption, image compression, dsp algorithm)
  • Data exchange with the upper cpu.

In some medium-sized or large-scale projects, under the hardware platform, fpga and cpu work together to complete certain specific functions. Therefore, it is often divided into the following types of modules

  • FPGA Function IP
  • FPGA registers
  • CPU bus interface
  • CPU software driver
  • CPU software application

 

FPGA functional IP: Complete specific functional modules and behaviors.

FPGA register: provide parameter configuration of IP module interface, read the relevant content of IP module

CPU bus interface: link FPGA register and cpu bus, provide read signal, write signal, address, data read, data write, etc.

CPU software driver: The software driver is used to configure the FPGA registers, and through the operation of the registers, the software can read and write the FPGA IP.

CPU software application: call the software driver to realize the upper-layer application function.

 

2. Packaging of FPGA IP:

Here we take the 7-segment digital tube as an example to realize the design process from IP core to software application.

`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: Used to set the 1ms clock, the user can set the 1ms clock through MS_CNT according to different clocks.

seg_in_x : for user data input interface

dp_in : the point used by the user to input the 7-segment digital tube

SEG: 7-segment digital tube output

DP: digital tube point output

SEAT: nixie tube position pin output

3. Register interface design:

According to different applications, different register interfaces can be designed.

register address register description Register initial value Literacy
16’h0000 Module Version Register 32’h0000_0001 read only
16’h0004 Module Control Register 32’h0000_0000 read and write
16’h001c data 1 register 32’h0000_0000 read and write
16’h0020 data 2 register 32’h0000_0000 read and write
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

 

Module version register (00h): read by cpu to display the current module version

Module Control Register (04h): Module Control Register,

bit[30] , 1 => update 7-segment digital tube value, when the cpu writes, the fpga side is automatically cleared

bit[5:0] indicates the position of the decimal point of the 7-segment digital tube.

Data 1 register (1ch): 7-segment digital tube value

Data 2 register (20h): 7-segment digital tube value

4. CPU interface design:

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};
//===============================================================================
endmodule

 

RISCV RIB bus:

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 bus translates to address, read, write, etc. signals:

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};
//==================================================== =================================

After some simple logic conversion, the conversion from bus to read and write logic is realized.

5. Software driver:

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

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

SEG_REG(0x04) = 0xc0000008;

 

The driver first writes the value of the 7-segment digital tube into the register, and finally writes the control register. The control register includes the position of the decimal point and bit [30] update information.

6. Software application:

/*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
}

 

The upper application handle_m_time_interrupt() calls the lower driver.

 

At this point, the underlying Verilog IP development and the calling process for the upper-level software application have been implemented.

Posted in FPGA, RISC-V, RISC-V Textbook, Textbook and Training Project

Related Articles

Leave a Reply

Your email address will not be published.

Leave the field below empty!