在实际应用开发中,在verilog层面上开发完成简单的功能后,如果遇到比较复杂的逻辑,就会借助类似NIOS这样的片上CPU(其实本身就是一个IP核),在上面应用C语言进行编写操作。
那么问题就有了,在verilog中已经处理好的数据怎么传到NIOS中给C语言处理?或者从NIOS传到Verilog层面上去呢?
我将应用一个简单例子说明一下。两者在通信中借助AVALON总线,哪怕简单理解成一条线,把两者连接起来。如果NIOS想得到Verilog中的数据,就执行读总线(这个读过程是要带地址的),总线会根据给出的地址找到你想读的寄存器,并把值拿到NIOS中。相反,如果想把数据发送给Verilog,就执行写总线操作(写过程同样是要带地址的,就是目标地址),总线就会根据给出的地址把数据发送到给定的地址上,也就发到了对应的寄存器。
简单理解就是下图,两边的寄存器都挂在AVALON总线上,所有的地址都可以通过AVALON总线找到,这里的地址是由自己定的,后面告诉你怎么定。
本文实现现象:从NIOS II中发送变量值到Verilog中,控制灯的闪烁。
请注意:不是用的PIO!!!
1.首先在Verilog中建立闪灯的逻辑
module data_deal(
clk,
rst,
data_in,
data_out
);
input clk;
input rst;
input [7:0] data_in;//NIOS发送过来的控制值
output reg data_out;//FPGA引脚输出,相当于是Verilog中的变量,这个值也可以被NIOS 读取
always@(posedge clk or negedge rst)
if(!rst)
data_out <= 1'b1;
else if(data_in == 8'd5) //当NIOS中发送过来的值为5,就亮灯,否则,灭灯。
data_out <= 1'b0;
else
data_out <= 1'b1;
endmodule
注释写在代码中,就不分析了。重点说下实现avalon总线的内容。
/*******************说明**********************/
/*这个module就是实现的通过avalon总线进行数据交互的功能*/
/*整个的模块可以说是一个通用标准了,使用中只需要修改成自己的变量即可*/
/*这里的as_writedata 和as_readdata 这两个数据寄存器,理解成NIOS可以操作的寄存器*/
/*往as_writedata里写数据就是发送给Verilog,读as_readdata就是从Verilog取数据*/
module av_data(
clk,
rst,
led_out,
as_address, //地址,这个地址就是用来给Verilog中的变量和NIOS中的变量确定地址的
as_chipselect, //设备选择
as_write, //写请求,NIOS向Verilog中写数据
as_writedata, //要写数据
as_read, //读请求,NIOS从Verilog中读数据
as_readdata //读取的数据
);
input clk;
input rst;
output led_out;
input [2:0] as_address;//地址位宽根据你需要操作多少个变量决定
input as_chipselect;
input as_write;
input [15:0] as_writedata; //操作数的位宽,一般定义成16bit
input as_read;
output reg [15:0] as_readdata;
//写数据
reg [7:0] data;
always@(posedge clk or negedge rst)
if(!rst)
data <= 1'b0;
else if((as_chipselect && as_write && (as_address == 0)))//注意这里的写条件,当地址为0的时候,就通过as_writedata这个写寄存器
data <= as_writedata[7:0]; //把数据写到Verilog中的data这个变量
//读数据
always@(posedge clk or negedge rst)
if(!rst)
as_readdata <= 16'd0;
else if(as_chipselect && as_read)begin
case(as_address)
0:as_readdata <= {8'd0, data};//同样还是根据地址,将Verilog中的变量值读到读寄存器中
1:as_readdata <= {15'd0, led_out};
default:as_readdata <= 16'd0;
endcase
end
//例化的控制亮灯的模块
data_deal data_deal(
.clk(clk),
.rst(rst),
.data_in(data),
.data_out(led_out)
);
endmodule
重点也都备注在代码中,还有不懂的可以一起讨论。两个关键的模块写好了,我们在工程中建立一个名为“ip”的文件夹,将上面的两个模块放到这个文件夹中,因为后续操作的时候,系统需要识别到ip文件夹才能操作。
上面都操作完成,再添加一个用于简单逻辑控制与引脚定义的顶层文件。
module nios_test(
clk,
rst,
work_led,
led_out,
uart_tx,
uart_rx
);
input clk;
input rst;
output reg work_led;
output led_out;
input uart_rx;
output uart_tx;
//500ms计时 25000000
reg [24:0] cnt;
always@(posedge clk or negedge rst)
if(!rst)
cnt <= 1'b0;
else if(cnt == 25'd24999999)
cnt <= 1'b0;
else
cnt <= cnt +1'b1;
always@(posedge clk or negedge rst)
if(!rst)
work_led <= 1'b1;
else if(cnt == 25'd24999999)
work_led <= ~work_led;
endmodule
上面一段代码主要就是将工程中需要连接的引脚进行定义,并点亮了一颗LED用于显示程序运行情况。全部写完再进行一次全编译。
接下来开始在qsys中进行操作,打开qsys平台,如下:
打开之后在按下操作:
知道我么要干什么吗?建立一个IP核,没错,自己都会建IP核了,了不得了。
按部就班操作,在file一栏要注意一下:将刚刚建立的两个module文件添加进来,并要执行一次分析综合,你可能会遇到报错,应该不是可能是肯定会,不要怕,接着往下。
到这一步的时候,你或许不会像我的这么整齐,因为我是已经调好的。你是不是发现这里显示的值都很眼熟,是的,这里的引脚可都是我们自己定义的,既然都是自己定义的那不要太了解了。但是你要做的,是要修改成正确的类型,类型特别特别重要,因为自动识别(就是上面建立ip文件夹的作用)不会很准确的,你要按照图中将每个引脚的类型对应上才可以。
红框中标注了特别主要注意的地方,reset引脚要注意是reset还是reset_n。这个坑搞了我一晚上没睡着,惭愧。提示有Associated Reset的地方要选上reset,这里不会默认选上,不选不行。
关注一下led_out这个脚,这个是需要连接到设备的实际的引脚,需要定义成conduit类型,这样才可以导出并连接。
全部执行完成后FINISH。然后双击刚刚建立的IP核,添加到NIOS系统中
一定要注意,led_out要双击导出。然后执行重要的地方,将NIOS例化到第三个引脚定义的模块中去。不要忘了Generate!
所以上面的代码就成了下面的样子:
module nios_test(
clk,
rst,
work_led,
led_out,
uart_tx,
uart_rx
);
input clk;
input rst;
output reg work_led;
output led_out;
input uart_rx;
output uart_tx;
//500ms计时 25000000
reg [24:0] cnt;
always@(posedge clk or negedge rst)
if(!rst)
cnt <= 1'b0;
else if(cnt == 25'd24999999)
cnt <= 1'b0;
else
cnt <= cnt +1'b1;
always@(posedge clk or negedge rst)
if(!rst)
work_led <= 1'b1;
else if(cnt == 25'd24999999)
work_led <= ~work_led;
//相比较上面的程序,就多了这一块
mysystem u0 (
.clk_clk (clk), // clk.clk
.reset_reset_n (rst), // reset.reset_n
.uart_0_rxd (uart_rx), // uart_0.rxd
.uart_0_txd (uart_tx), // .txd
.av_sys_data_0_led_out(led_out)
);
endmodule
然后整个工程全编译,ctrl+L即可。
然后打开eclipse
写代码
#include "sys/alt_stdio.h"
#include "io.h" //这个头文件就是操作读写数据寄存器的需要
#include "system.h"
#include "unistd.h"
int main()
{
alt_putstr("Hello from Nios II!\n");
/* Event loop never exits. */
while (1)
{
IOWR_16DIRECT(AV_SYS_DATA_0_BASE,0,0x05);//通过这个写函数,向地址0上写一个5,对应Verilog中就会写到data中,此时data_out输出为0,灯亮
printf("data is %x\r\n",IORD_16DIRECT(AV_SYS_DATA_0_BASE,2));//注意这里的读函数,地址写的是2,我们在Verilog中定义的读led_out的地址是1
usleep(100000); //这个就是地址对应的关系,Verilog中是16bit,NIOS中是8bit,需要2个对应上1个
IOWR_16DIRECT(AV_SYS_DATA_0_BASE,0,0x01); //写一个1,灯会灭
printf("data is %x\r\n",IORD_16DIRECT(AV_SYS_DATA_0_BASE,2));//读出Verilog中灯的输出状态
usleep(100000);
}
return 0;
}
代码说明也在注释中,看下就可,烧录后对应灯会闪烁,串口也会打印出led_out引脚对应的数值。
以上就实现了Verilog与NIOS双向通信的过程,附上测试工程,供大家参考。
链接:https://pan.baidu.com/s/1MBQ8Yd6Q1Kl2rjjC-Kl3Sw
提取码:wrcy