本教程介绍设计自定义IP组件并添加到Platform Designer系统,设计基于Nios V的软件工程,控制DE2-115开发板上的8个七段数码管循环显示十六进制数0~F。
一、 系统功能实现
本实验的Nios V系统由Nios V/m处理器、On-Chip Memory内存、JTAG接口和自定义七段数码管IP组成。该系统可使用Platform Designer工具来实现,Platform Designer工具不仅包含像Nios V/m Processor、JTAG UART、PIO等组件,还可以借助Platform Designer自定义添加IP组件。
自定义SEG7_IF IP组件
DE2-115 CD-ROM/DE2_115_demonstrations/DE2_115_golden_sopc/ip/TERASIC_SEG7,这是本实验所用的IP组件并再稍作修改(后续会有说明)。
关于七段数码管的控制显示原理,还可以参考《基于FPGA的贪吃蛇游戏设计(二)——数码管驱动模块》。
SEG7_IF组件的顶层信号接口如下表:clk和reset_n为该组件的时钟和复位信号;s_address、s_write、s_writedata、s_read、s_readdata为该组件的Avalon-MM接口,通过该接口实现与Nios V处理器的数据读写;SEG7为该组件的Avalon-Conduit接口,该接口被导出到顶层设计,并与DE2-115开发板上的8个七段数码管相连接。
下图为该组件的模块示意图,每个七段数码管都有一个寄存器用来存放要显示的十六进制数0 ~ F。可以看出SEG7_IF模块主要由模块对外接口和寄存器存储电路组成。
SEG7_IF组件端口
模块端口主要分为Clock/Reset接口、Avalon-MM Slave接口和Avalon Conduit接口,以下是模块的端口信息,定义了一些参数如数码管的个数、Avalon-MM Slave读写地址的宽度、复位后所有七段数码管的状态、数码管是高电平还是低电平点亮,提高了可重用性。
module SEG7_IF(
//===== avalon MM s1 slave (read/write)
// write
s_clk,
s_address,
s_read,
s_readdata,
s_write,
s_writedata,
s_reset,
//
//===== avalon MM s1 to export (read)
// read/write
SEG7
)
;
/*****************************************************************************
* Parameter Declarations *
*****************************************************************************/
parameter SEG7_NUM = 8;
parameter ADDR_WIDTH = 3;
parameter DEFAULT_ACTIVE = 1;
parameter LOW_ACTIVE = 1;
......
/*****************************************************************************
* Port Declarations *
*****************************************************************************/
// s1
input s_clk;
input [(ADDR_WIDTH-1):0] s_address;
input s_read;
output [7:0] s_readdata;
input s_write;
input [7:0] s_writedata;
input s_reset;
//===== Interface to export
// s1
output [(SEG7_NUM*8-1):0] SEG7;
SEG7_IF组件寄存器读写
寄存器存储电路定义了SEG7_NUM个8位寄存器reg_file,每个寄存器存储一个数码管显示的字符,如reg_file[0]存储数码管HEX0上要显示的字符,reg_file[1]存储数码管HEX1上要显示的字符。复位时寄存器存储的数据由参数DEFAULT_ACTIVE决定,当DEFAULT_ACTIVE=1时,所有寄存器存储的数据为1'b1,即所有的数码管熄灭不显示;DEFAULT_ACTIVE=0时,所有的寄存器存储的数据为1'b0,即所有的数码管会显示字符“8”。
当进行写操作时,Avalon-MM Slave接口的写数据s_writedata将会存储到地址为s_address的寄存器中;当进行读操作时,会将地址为s_address的寄存器中的数据读取至s_readdata中。
/*****************************************************************************
* Internal Wire/Register *
*****************************************************************************/
reg [7:0] base_index;
reg [7:0] write_data;
reg [7:0] read_data;
reg [(SEG7_NUM*8-1):0] reg_file;
/*****************************************************************************
* Sequence logic *
*****************************************************************************/
always @ (negedge s_clk)
begin
if (s_reset)
begin
integer i;
for(i=0;i<SEG7_NUM*8;i=i+1)
begin
reg_file[i] = (DEFAULT_ACTIVE)?1'b1:1'b0; // trun on or off
end
end
else if (s_write)
begin
integer j;
write_data = s_writedata;
base_index = s_address;
base_index = base_index << 3;
for(j=0;j<8;j=j+1)
begin
reg_file[base_index+j] = write_data[j];
end
end
else if (s_read)
begin
integer k;
base_index = s_address;
base_index = base_index << 3;
for(k=0;k<8;k=k+1)
begin
read_data[k] = reg_file[base_index+k];
end
end
end
/*****************************************************************************
* Combinational logic *
*****************************************************************************/
assign SEG7 = (LOW_ACTIVE)?reg_file:~reg_file;
assign s_readdata = read_data;
SEG7_IF组件8个寄存器都是8-bit,可以使用IORD/IOWR来读写这8个寄存器。例如若要使数码管HEX0显示字符“F”,则要向第0个寄存器中写入数值0x0F,使用以下语句,其中SEG7_IF_BASE为Seg7_IF组件的基地址:
IOWR(SEG7_IF_BASE, 0, 0x0F) //七段数码管HEX0显示字符“F”
若要使数码管HEX1显示字符“A”,则要向第1个寄存器中写入数值0x0A,使用以下语句,以此类推:
IOWR(SEG7_IF_BASE, 1, 0x0A) //七段数码管HEX1显示字符“A”
Nios V .c应用程序
要控制七段数码管,除了需要SEG7_IF组件的驱动程序,还需要完整的Nios V .c应用程序。在应用程序的开头,要包含必要的头文件。
#include <stdio.h>
#include "system.h"
#include "io.h"
#include "alt_types.h"
stdio.h文件提供了标准输入输出函数比如printf函数等,所以要包含这个头文件。IOWR函数定义在io.h文件内。alt_types.h定义了数据类型,下面是alt_types.h文件内容的片段,比如我们程序里面定义了HEX_display类型为alt_u16 ,实际上就是定义一个无符号字符类型的16位数据。
#ifndef ALT_ASM_SRC
typedef signed char alt_8;
typedef unsigned char alt_u8;
typedef signed short alt_16;
typedef unsigned short alt_u16;
typedef signed long alt_32;
typedef unsigned long alt_u32;
typedef long long alt_64;
typedef unsigned long long alt_u64;
#endif
system.h提供了关于Nios V系统硬件信息的描述,是硬件和软件之间的桥梁。
以下代码定义并驱动七段数码管显示十六进制数0~F,比如HEX_display[0]为十进制数64,转换为二进制数为1000000,七段数码管显示即为0,依此类推HEX_display[15]为十进制数14,表示数码管显示十六进制数F。
/* 数码管控制数组HEX0~HEX7循环显示0~F,低电平点亮*/
const alt_u64 HEX_display[] = {
64, //7'b1000000,数码管显示0
121, //7'b1111001,数码管显示1
36, //7'b0100100,数码管显示2
48, //7'b0110000,数码管显示3
25, //7'b0011001,数码管显示4
18, //7'b0010010,数码管显示5
2, //7'b0000010,数码管显示6
120, //7'b1111000,数码管显示7
0, //7'b0000000,数码管显示8
24, //7'b0011000,数码管显示9
8, //7'b0001000,数码管显示a
3, //7'b0000011,数码管显示b
70, //7'b1000110,数码管显示c
33, //7'b0100001,数码管显示d
6, //7'b0000110,数码管显示E
14 //7'b0001110,数码管显示F
};
在main()函数中,先使用printf语句打印输出“Realize the display function of eight HEX from 0 to F”信息,然后定义了一些变量。
int main(){
printf("Realize the display function of eight HEX from 0 to F\n");
alt_u16 i;
alt_u32 delay;
然后是while()循环语句,对8个七段数码管进行写操作,将HEX_display[0]~HEX_display[15]这十六个值写入8个数码管,使数码管循环显示0-F。
while(1){
for(i = 0; i<16; i++){ //HEX共有16种状态
IOWR(SEG7_IF_BASE, 0, HEX_display[i]); //对HEX0进行写操作
IOWR(SEG7_IF_BASE, 1, HEX_display[i]); //对HEX1进行写操作
IOWR(SEG7_IF_BASE, 2, HEX_display[i]); //对HEX2进行写操作
IOWR(SEG7_IF_BASE, 3, HEX_display[i]); //对HEX3进行写操作
IOWR(SEG7_IF_BASE, 4, HEX_display[i]); //对HEX4进行写操作
IOWR(SEG7_IF_BASE, 5, HEX_display[i]); //对HEX5进行写操作
IOWR(SEG7_IF_BASE, 6, HEX_display[i]); //对HEX6进行写操作
IOWR(SEG7_IF_BASE, 7, HEX_display[i]); //对HEX7进行写操作
delay = 0;
while(delay < 2000000)
delay++;
}
}
return 0;
}
二、创建Quartus工程
1. 参考《基于DE1-SOC的Nios V工程—My First Nios V》,使用Quartus Standard Edition v22.1创建Quartus硬件工程,FPGA器件选择DE2-115的Cyclone IV E EP4CE115F29C7,打开Quartus硬件工程。
2. 在Quartus工程路径下创建一个文件夹命名为ip,再将DE2-115 CD-ROM/DE2_115_demonstrations/DE2_115_golden_sopc/ip里的TERASIC_SEG7文件夹复制到刚才创建的ip文件夹里。
3. 打开SEG7_IF.v文件,输入以下代码替换。相对于源码,我们将assign SEG7 = (LOW_ACTIVE)?~reg_file:reg_file修改为assign SEG7 = (LOW_ACTIVE)?reg_file:~reg_file,这样保证后续的main.c程序里数码管驱动代码也以低电平点亮数码管。
/*******************************************************************************
功能: 控制七段数码管的显示
参数:
s_address: 七段数码管索引,如0指示第一个七段数码管,1指示第二个七段数码管
s_writedata: 映射到七段数码的8位二进制数(0:点亮, 1:熄灭)
0
------
| |
5| 6 |1
------
| |
4| |2
------ . 7
3
映射矩阵:
unsigned char szMap[] = {
64, 121, 36, 48, 25, 18, 2, 120,
0, 24, 8, 3, 70, 33, 6, 14
}; //依次表示十进制数0,1,2,....9, a, b, c, d, E, F
参数:
SEG7_NUM: 指定七段数码管的个数,默认值为8
ADDR_WIDTH: log2(SEG7_NUM),默认值为3
DEFAULT_ACTIVE:
复位后所有的七段数码管显示状态
1: 默认值,将所有七段数码管熄灭,即显示0
0: 将所有七段数码管显示8
LOW_ACTIVE:
1: 共阳数码管,即低电平点亮数码管
0: 共阴数码管,即高电平点亮数码管
******************************************************************************/
module SEG7_IF(
//===== avalon MM s1 slave (read/write)
// write
s_clk,
s_address,
s_read,
s_readdata,
s_write,
s_writedata,
s_reset,
//
//===== avalon MM s1 to export (read)
//read/write
SEG7
);
/*****************************************************************************
* Parameter Declarations *
*****************************************************************************/
parameter SEG7_NUM = 8;
parameter ADDR_WIDTH = 3;
parameter DEFAULT_ACTIVE = 1;
parameter LOW_ACTIVE = 1;
/*****************************************************************************
* Internal Wire/Register *
*****************************************************************************/
reg [7:0] base_index;
reg [7:0] write_data;
reg [7:0] read_data;
reg [(SEG7_NUM*8-1):0] reg_file;
/*****************************************************************************
* Port Declarations *
*****************************************************************************/
// s1
input s_clk;
input [(ADDR_WIDTH-1):0] s_address;
input s_read;
output [7:0] s_readdata;
input s_write;
input [7:0] s_writedata;
input s_reset;
//===== Interface to export
// s1
output [(SEG7_NUM*8-1):0] SEG7;
/*****************************************************************************
* Sequence logic *
*****************************************************************************/
always @ (negedge s_clk)
begin
if (s_reset)
begin
integer i;
for(i=0;i<SEG7_NUM*8;i=i+1)
begin
reg_file[i] = (DEFAULT_ACTIVE)?1'b1:1'b0; // trun on or off
end
end
else if (s_write)
begin
integer j;
write_data = s_writedata;
base_index = s_address;
base_index = base_index << 3;
for(j=0;j<8;j=j+1)
begin
reg_file[base_index+j] = write_data[j];
end
end
else if (s_read)
begin
integer k;
base_index = s_address;
base_index = base_index << 3;
for(k=0;k<8;k=k+1)
begin
read_data[k] = reg_file[base_index+k];
end
end
end
/*****************************************************************************
* Combinational logic *
*****************************************************************************/
assign SEG7 = (LOW_ACTIVE)?reg_file:~reg_file;
assign s_readdata = read_data;
endmodule
三、创建Nios V系统
1. 参考《基于DE1-SOC的Nios V工程——My First Nios V》,添加Nios V/m Processor Intel FPGA IP、On-Chip Memory (RAM or ROM) Intel FPGA IP以及JTAG UART Intel FPGA IP。其中On-Chip Memory (RAM or ROM) Intel FPGA IP的Total Memory Size修改为204800,其余IP等参数不变。
2. 添加自定义SEG7_IF IP组件:在Terasic Technologies Inc./DE2-115里可以看到自定义创建的SEG7_IF IP组件,双击添加该IP,保持默认参数不变。
3. 依次将clk_0、intel_niosv_m_0、onchip_memory2_0、jtag_uart_0以及SEG7_IF_0重命名为clk、intel_niosv_m、onchip_memory2、jtag_uart以及SEG7_IF。
4. 按下图所示连接各IP,最后双击箭头处将SEG7_IF导出。
5. 修改Nios V/m IP的参数设置,勾选Enable Reset from Debug Module,Reset Agent选择onchip_memory2.s1。
6. 点击Platform Designer的菜单System—>Assign Base Addresses给每个模块分配地址空间。
7. Message栏仍然有错误提示,将intel_niosv_m的ndm_reset_in信号与系统reset信号连接起来。最后点击窗口右下角的Generate HDL按钮生成qsys文件。
8. 点击Quartus菜单Project—>Add/remove Files in Project,将niosv.qsys文件就可以将该文件添加进工程里面去。
9. 点击Quartus菜单File—>New—>Verilog HDL File—>OK,创建niosv_hex.v顶层文件。在该.v文件里面输入如下代码并保存。
module niosv_hex(
input CLOCK_50,
output [6:0] HEX0,
output [6:0] HEX1,
output [6:0] HEX2,
output [6:0] HEX3,
output [6:0] HEX4,
output [6:0] HEX5,
output [6:0] HEX6,
output [6:0] HEX7
);
7-SEG //
wire HEX0P;
wire HEX1P;
wire HEX2P;
wire HEX3P;
wire HEX4P;
wire HEX5P;
wire HEX6P;
wire HEX7P;
niosv u0 (
.clk_clk(CLOCK_50), //clk.clk
.seg7_if_conduit_end_export({
HEX7P,HEX7,HEX6P,HEX6,
HEX5P,HEX5,HEX4P,HEX4,
HEX3P,HEX3,HEX2P,HEX2,
HEX1P,HEX1,HEX0P,HEX0}), // hex_decoder_x8_conduit_end.new_signal
.reset_reset_n(1'b1) //reset.reset_n
);
endmodule
10. 点击Analysis & Synthesis进行分析与综合。
11. 按DE2-115开发板的相关引脚进行引脚分配,最后再点击Start Compilation编译Quartus工程,最终生成.sof文件。
四、创建Niov软件工程
1. 参考《基于DE1-SOC的Nios V工程——My First Nios V》,在niosv_hex工程文件夹路径下新建software文件。
2. 运行niosv-bsp -c -t=hal --sopcinfo=niosv.sopcinfo software/hex_bsp/settings.bsp命令在software文件夹创建Nios V BSP。
3. 在software文件夹路径下再创建hex文件夹,并在hex文件夹下创建空白main.c文件。
4.运行niosv-app -a=software/hex -b=software/hex_bsp -s=software/hex/main.c命令在hex文件夹生成CMakeLists.txt文件。
5. 打开Quartus v22.1.2下的RiscFree IDE,workspace指向software文件夹路径。然后点击Launch启动软件。
6. 点击Create a project,在New Project窗口,选择C/C++下的C Project;在打开的C Project窗口,Project name命名为hex,Location指向hex所在文件夹,Project Type选择CMake driven下的Empty Project;接下来的Select Configurations窗口保持默认设置不变,点击Finish即可。
7. 在main.c文件里面输入以下代码并保存。
#include <stdio.h>
#include "system.h"
#include "io.h"
#include "alt_types.h"
/* 数码管控制数组HEX0~HEX7循环显示0~F,低电平点亮*/
const alt_u64 HEX_display[] = {
64, //7'b1000000,数码管显示0
121, //7'b1111001,数码管显示1
36, //7'b0100100,数码管显示2
48, //7'b0110000,数码管显示3
25, //7'b0011001,数码管显示4
18, //7'b0010010,数码管显示5
2, //7'b0000010,数码管显示6
120, //7'b1111000,数码管显示7
0, //7'b0000000,数码管显示8
24, //7'b0011000,数码管显示9
8, //7'b0001000,数码管显示a
3, //7'b0000011,数码管显示b
70, //7'b1000110,数码管显示c
33, //7'b0100001,数码管显示d
6, //7'b0000110,数码管显示E
14 //7'b0001110,数码管显示F
};
//
//
// function: main
// description: perform cyclically lighting the eight HEXs from 0 to F
// parameter: none
// return: 0
//
//
int main(){
printf("Realize the display function of eight HEX from 0 to F\n"); //在Nios V console窗口打印信息
alt_u16 i;
alt_u32 delay;
while(1){
for(i = 0; i<16; i++){ //HEX共有16种状态
IOWR(SEG7_IF_BASE, 0, HEX_display[i]); //对HEX0进行写操作
IOWR(SEG7_IF_BASE, 1, HEX_display[i]); //对HEX1进行写操作
IOWR(SEG7_IF_BASE, 2, HEX_display[i]); //对HEX2进行写操作
IOWR(SEG7_IF_BASE, 3, HEX_display[i]); //对HEX3进行写操作
IOWR(SEG7_IF_BASE, 4, HEX_display[i]); //对HEX4进行写操作
IOWR(SEG7_IF_BASE, 5, HEX_display[i]); //对HEX5进行写操作
IOWR(SEG7_IF_BASE, 6, HEX_display[i]); //对HEX6进行写操作
IOWR(SEG7_IF_BASE, 7, HEX_display[i]); //对HEX7进行写操作
delay = 0;
while(delay < 2000000)
delay++;
}
}
return 0;
}
8. 左键选中hex,点击Build Project编译main.c,编译完成可以看到生成hex.elf文件。
五、 配置FPGA与测试
1. 点击Quartus软件工具栏的Tools--> Programmer,烧录Quartus硬件工程的niosv_hex.sof文件。
2. 在Nios V Command Shell里运行juart-terminal。
3. 在RiscFree IDE窗口左键选中hex,选择菜单里的Run As-->3 Ashling RISC-V(auto-detect)Hardware Debugging,烧录.elf并运行程序。
4. 运行完成后即可在DE2-115开发板上看到8个七段数码管循环显示0~F;同时在Nios V Command Shell会打印输出:Realize the display function of eight HEX from 0 to F。