在上一期的分享中阐述了使用自定义IP核的方式实现NIOS与Verilog中的数据交互方式。在该方法中,基于AVALON总线的读写函数需要作为顶层文件,其他包含有需要操作的数据的模块都必须作为子模块来操作,这种是很不方便的。
举例来说就是,对于一个已经实现好读温度值的verilog程序,如果想把verilog中读到的温度值通过nios中发送出去,那么就需要重新再建一个avalon的module,将已经实现的程序当成子模块给avalon module例化才行,这样的改动会很大的,那么有没有一种方法可以在不动源程序的基础上,把已经得到的值发送出去呢?答案是肯定的。
本篇的方法较上一个方法会简单很多。
在此之前,我需要引用上一篇中的一些概念:conduit。这个是针对用户操作的管脚,上一篇中,我们利用这个地方当成了连接LED的管脚,配置的时候实际这个是连接到了FPGA的管脚上的。在此篇中,我们还是会使用conduit,但是我们不必将它连接到实际的FPGA引脚,而是把它当成一个寄存器。那么就看下怎么实现这个寄存器功能。
本篇中,我们将使用PIO实现:从nios中向verilog中写数据,再从verilog中将写入的数据读出来的功能。
既然说到了使用PIO实现,那我么主要讨论下PIO这一块的内容。通常来说,我们在NIOS中使用PIO,是要操作对应的IO口,控制灯的亮灭,控制外设的通信等。当然,上面的操作就需要将配置的PIO映射到对应的FPGA引脚上,但是我们在本例中使用的PIO,是当成寄存器使用的,尤其要注意理解这个寄存器,这里是不需要将PIO映射到FPGA引脚上的。(我在这之前一直以为PIO一定要映射到真实的管脚上)
首先看下qsys中配置的PIO吧:
对应上图,配置的data_out是使用的PIO配置的一个8bit的寄存器,表明一次可以发送一个8bit的数据;配置中需要注意的是IO的方向,如果是从NIOS向Verilog中发送数据,就配置成output,反之就配置成input;当把PIO配置成寄存器使用时,不要动下面的中断配置。
我上面已经配置了两个,一个data_out是用来从nios中发送数据的,一个data_in是用来从verilog中读数据的。
好,可以上程序分析了
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;
//建一个测试用的
reg [7:0] test_signal;
wire [7:0] test_data_out;
wire [7:0] test_data_in;
always@(posedge clk or negedge rst)
if(!rst)
test_signal <= 1'b0;
else
test_signal <= test_data_out;//test_signal是用来存nios中发送过来的数据的
assign test_data_in = test_signal;//同时又将保存的nios中的数据发送给nios,如果两个值相等,证明测试成功。
//例化的qsys
mysystem u0 (
.clk_clk (clk), // clk.clk
.reset_reset_n (rst), // reset.reset_n
.uart_0_rxd (uart_rx), // uart_0.rxd 这里的uart是连接的FPGA的两个引脚,通过一个TTL转USB模块可以在串口打印工具上打印信息
.uart_0_txd (uart_tx), // .txd
.av_sys_data_0_led_out(led_out),//上一篇中的led管脚
.test_data_out_export (test_data_out), // nios中发送来的数据
.test_data_in_export (test_data_in) //nios读verilog中的数据
);
endmodule
该程序是基于上一版的修改,控制led的程序没动,只是增加了PIO通信部分的内容,整个的工程需要修改的不多。前面的主体还是一个亮灯的程序,每500ms闪一次,指示程序的运行;后面例化的是qsys生成的module,在module中增加了两个导出的端口,test_data_out_export和test_data_in_export这两个,也就是我们配置的PIO的端口,需要破除的误区就是,这里的导出的两个PIO端口是当成寄存器使用的,并不是实际连接的引脚,也就是说,可以进行module之间的数据交换使用。
再看下nios中需要修改哪些地方:
#include "sys/alt_stdio.h"
#include "io.h" //这个头文件就是操作读写数据寄存器的需要
#include "system.h"
#include "unistd.h"
#include "altera_avalon_pio_regs.h"
int main()
{
alt_putstr("Hello from Nios II!\n");
/* Event loop never exits. */
while (1)
{
/*******************avalon自定义IP读写的测试***********************/
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);
/********************PIO的读写测试********************/
IOWR_ALTERA_AVALON_PIO_DATA(DATA_OUT_BASE,0xaa);//向verilog中写一个0xaa
printf("data in data in:%x\r\n",IORD_ALTERA_AVALON_PIO_DATA(DATA_IN_BASE));//从verilog中读出刚刚写的0xaa,并打印出来
}
return 0;
}
C中修改的同样不多,仅仅是调用了两个PIO读写的函数,将数据0Xaa写到Verilog的数据中,然后再读回来。
实际测试结果这里就不用展示了,当然是正确的。
看到这里,是不是发现比上一篇的方法少了很多配置,这就是利用PIO当寄存器实现NIOS与Verilog数据交互的方法。不知道大家注意到没有,在配置PIO的时候,数据位数的配置只能配置到32bit,这就意味着,一次传输最多32bit数据。这个同样又造成了困扰。那么你们有方法可以传输多字节数据吗?该方法将在后续揭晓。