【Avalon总线】4.avalon总线MM设备读写程序实例及分析
1 引言
本文将通过作者写的程序对avalon总线进行描述,相信会对avalon总线有更加深的认识。
2 模块连接和源代码
图1给了编写的模块和NIOS核之间的互联关系。(图中只示出了与本文相关的模块,同时未示出总线)。代码将实现如下的功能:
1、 NIOS核可直接对master_slave和slave直接进行读写。
2、 NIOS核可以控制master_slave对slave进行读写。
图1 模块连接示意图
直接上代码。分析在文章后半部分
代码1:master_slave模块的硬件源码
module master_slave(
clk,reset_n,
address,write,writedata,read,readdata,
maddress,mwrite,mwritedata,mread,mreaddata,mwaitrequest
);
input clk;
input reset_n;
// 从接口信号
input[1:0] address;
input write;
input [31:0] writedata;
input read;
output [31:0] readdata;
// 主接口信号
output[31:0] maddress;
output mwrite;
output[31:0] mwritedata;
output mread;
input[31:0] mreaddata;
input mwaitrequest;
reg [31:0] start_address;
reg [31:0] tramsfer_num;
reg [31:0] wr_control;
reg [31:0] data_from_slave;
reg start_address_selected;
reg tramsfer_num_selected;
reg wr_control_selected;
reg data_from_slave_selected;
reg [31:0] readdata;
// 地址译码
always @ (address)
begin
start_address_selected<= 0;
tramsfer_num_selected<= 0;
wr_control_selected<= 0;
data_from_slave_selected<= 0;
case(address)
2\’b00:start_address_selected<= 1;
2\’b01:tramsfer_num_selected<= 1;
2\’b10:wr_control_selected<= 1;
2\’b11:data_from_slave_selected<= 1;
default:
begin
start_address_selected<= 0;
tramsfer_num_selected<= 0;
wr_control_selected<= 0;
data_from_slave_selected<= 0;
end
endcase
end
// 从接口:写初始地址寄存器
always @ (posedge clk or negedge reset_n)
begin
if(reset_n==1\’b0)
start_address<= 0;
else
begin
if(write& start_address_selected)
begin
start_address<= writedata;
end
end
end
// 从接口:写读写个数寄存器
always @ (posedge clk or negedge reset_n)
begin
if(reset_n==1\’b0)
tramsfer_num<= 0;
else
begin
if(write& tramsfer_num_selected)
begin
tramsfer_num<= writedata;
end
end
end
// 从接口:写读写控制寄存器
always @ (posedge clk or negedge reset_n)
begin
if(reset_n==1\’b0)
wr_control<= 0;
else
begin
if(write& wr_control_selected)
begin
wr_control<= writedata;
end
end
end
// 从接口:读寄存器
always @ (address or read or tramsfer_numor start_address)
begin
if(read)
case(address)
2\’b00:readdata<= start_address;
2\’b01:readdata<= tramsfer_num;
2\’b10:readdata<= wr_control;
2\’b11:readdata<= data_from_slave;
default:readdata<= 32\’h8888;
endcase
end
reg[31:0] maddress;
reg mwrite;
reg [31:0] mwritedata;
reg mread;
reg state_read;
reg state_write;
reg[1:0] num_read_down;
reg[1:0] num_write_down;
wire read_enable;
wire write_enable;
assign read_enable = wr_control[0];
assign write_enable = wr_control[1];
// 主接口读,写 : 地址信号,读信号,写信号
always @ (posedge clk or negedge reset_n)
begin
if(!reset_n)
begin
mread<= 1\’b0;
mwrite<= 1\’b0;
maddress<= 32\’d0;
state_read<= 1\’b0;
state_write<= 1\’b0;
num_read_down<= 2\’d0;
num_write_down<= 2\’d0;
end
else
begin
if(read_enable& (num_read_down < {tramsfer_num[1],tramsfer_num[0]}))//主接口读
begin
case(state_read)
1\’b0:
begin
mread<= 1\’b1;
maddress<= start_address + {num_read_down,2\’b00};
state_read<= 1\’b1;
end
1\’b1:
begin
data_from_slave= mreaddata;
mread<= 1\’b0;
maddress<= 32\’d0;
state_read<= 1\’b0;
num_read_down<= num_read_down + 1;
end
endcase
end
if(write_enable& (num_write_down < {tramsfer_num[1],tramsfer_num[0]}))//主接口写
begin
case(state_write)
1\’b0:
begin
mwrite<= 1\’b1;
maddress<= start_address + {num_write_down,2\’b00};
mwritedata<= 32\’d158;
state_write<= 1\’b1;
end
1\’b1:
begin
mwrite<= 1\’b0;
maddress<= 32\’d0;
mwritedata<= 32\’d0;
state_write<= 1\’b0;
num_write_down<= num_write_down + 1;
end
endcase
end
end
end
endmodule
代码2:slave模块的硬件源码
module slave(
clk,reset_n,
address,write,writedata,read,readdata,
);
input clk;
input reset_n;
// 从接口
input address;
input write;
input [31:0] writedata;
input read;
output [31:0] readdata;
reg [31:0] first_reg;
reg [31:0] second_reg;
reg first_reg_selected;
reg second_reg_selected;
reg [31:0] readdata;
// 地址译码
always @ (address)
begin
first_reg_selected<=0;
second_reg_selected<=0;
case(address)
1\’b0:first_reg_selected<=1;
1\’b1:second_reg_selected<=1;
default:
begin
first_reg_selected<=0;
second_reg_selected<=0;
end
endcase
end
// 从接口:写第一个寄存器
always @ (posedge clk or negedge reset_n)
begin
if(reset_n==1\’b0)
first_reg=0;
else
begin
if(write& first_reg_selected)
begin
first_reg=writedata;
end
end
end
// 从接口:写第二个寄存器
always @ (posedge clk or negedge reset_n)
begin
if(reset_n==1\’b0)
second_reg=0;
else
begin
if(write& second_reg_selected)
begin
second_reg=writedata;
end
end
end
// 从接口:读寄存器
always @ (address or read or second_reg orfirst_reg)
begin
if(read)
case(address)
2\’b00:readdata<=first_reg;
2\’b01:readdata<=second_reg;
default:readdata=32\’h0000;
endcase
end
endmodule
代码3:NIOS读写软件源代码
#include <stdio.h>
#include “system.h”
#include <unistd.h>
typedef struct{
unsigned int start_address;
unsigned int tramsfer_num;
unsigned int wr_control;//wr_control[0]控制读(1为开始读),wr_control[1]控制写(1为开始写)
unsigned int data_from_slave;
}MASTER;
typedef struct{
unsigned int first_reg;
unsigned int second_reg; //32位
}SLAVE;
int main()
{
int temp1;
int temp2;
MASTER *master = (MASTER *) TEST3_0_BASE;
SLAVE *slave = (SLAVE *) TEST3_SLAVE_0_BASE;
//初始化从接口寄存器(slave)
slave->first_reg = 0xf3;
slave->second_reg = 0xf1;
//主接口(master_slave)读从接口(slave)
master->start_address = TEST3_SLAVE_0_BASE;//给主接口需要读的地址
master->tramsfer_num = 1;//给主接口需要读的数据的个数
master->wr_control = 1;//读使能置位
master->wr_control = 0;//读使能清零
temp1 = master->data_from_slave;//读master_slave从slave读到的数据
printf(“%d”,temp1);
//主接口写从接口
master->start_address = TEST3_SLAVE_0_BASE;/ 给主接口需要写的地址
master->tramsfer_num = 2;// 给主接口需要写的数据的个数
master->wr_control= 2;//写使能置位
master->wr_control= 0;//写使能清零
temp1 = slave->first_reg;//读master_slave从slave读到的数据
temp2 = slave->second_reg;// 读master_slave从slave读到的数据
return 0;
}
3 建立工程
具体工程建立的过程再次仅仅做简单的介绍,把重点放在对代码关键部分讲解上。在QuartusII,SOPC Builder中搭建好硬件,在硬件中添加之前提到的master_slave和slave。
图2 SOPC Builder建立的硬件系统
在NIOS II IDE中建立新工程,在主程序中写入NIOS读写软件源代码,编译运行能看到如下的结果:
图3 程序运行结果
4 关键代码讲解
代码2的slave是一个从接口,可以实现读写。代码1的master_slave有一个从接口,一个主接口,从接口几乎和代码2相同,实现的效果也相同。同时,代码1还有一个主接口,可以实现对从设备的读写。因此将对代码1进行解读。需要指出的是两个代码的读写都是典型的读写,没有用到流传输或者是突发传输。
master_slave中有四个寄存器,可以在图2中看出,master_slave的地址是从0x00001800-0x0000180f。Master_slave从接口有如下信号:address,write,writedata,read,readdata。主接口有如下信号:address,write,writedata,read,readdata,waitrequest。两个接口都是32位的。需要指出的是主接口有waitrequest,但是程序中并没有用到这个信号,如果不加这个信号在自定义IP核时,SOPC
Builder会报错,不知道这是不是一个bug。所以在信号中加入了waitrequest信号,同是该信号始终接地,不会影响读写过程。
图4 master_slave模块信号
4.1 地址译码段
avalon总线传输过来地址,当地址偏移为0时选中第一个寄存器(start_address_reg),当偏移为1时选中第二个寄存器(tramsfer_num_reg)……
4.2 从接口写传输段
在模块中的4个寄存器只有前3个寄存器可以被写,在时钟上升沿,并且write信号有效,寄存器被选中,则写入信号。
4.3 从接口读传输段
模块中4个寄存器都可以被读,当读信号有效时,给总线数据。
4.4 主接口读写传输段
主接口读写传输需要符合avalon总线的时序。再本节中给出时序图,根据时序图能够较为清晰得看出为什么从接口读写过程中的敏感信号不同。
图5读传输过程时序图
图6 写传输过程时序图
4.5 软件代码部分
软件代码主要是实现对从设备的读写,采用结构体能够很好的对地址的偏移进行处理。如MASTER结构体将模块中的四个寄存器进行了定义,在赋值和读取时只要用到指针,可以使代码简单易读,编写上也会方便不少。
本篇错误在所难免,希望大家指出,有一些写得不是特别清楚的地方欢迎谈论,