AHB协议的verilog实现(无等待/单次传输)
一、AHB协议介绍
关于AHB协议的具体内容可以参考下面这篇文章:
下图是三个主机和四个从机的 AMBA AHB 设计结构。
典型的 AMBA AHB 系统设计包含以下的部分:
AHB 主机:总线主机能够通过提供地址和控制信息发起读写操作。任何时候只允许一个总线主机处于有效状态并能使用总线。
AHB 从机:总线从机在给定的地址空间范围内响应读写操作。总线从机将成功、失败或者等待数据传输的信号返回给有效的主机。
AHB 仲裁器:总线仲裁器确保每次只有一个总线主机被允许发起数据传输。即使仲裁协议已经固定,任何一种仲裁算法,比如最高优先级或者公平访问都能够根据应用要求而得到执行。AHB 必须只包含一个仲裁器,尽管在单总线主机系统中这显得并不重要。
AHB 译码器:AHB 译码器用来对每次传输进行地址译码并且在传输中包含一个从机选择信号。所有 AHB 执行都必须仅要求有一个中央译码器。
二、AHB一主四从系统设计
本系统实现的是一主四从,没有等待状态的单次传输(Single Transfer)。系统框图如下图所示:
因为是一主四从,所以就没有设计仲裁器!!!而且设计中使用的都是一些基本的信号,很多额外的功能信号都没有在代码中出现!!!
三、代码设计
需要注意的是,代码中使用32位地址haddr
的高两位作为从机选择信号,然后低5位作为实际的访存地址。在这里只是为了检验系统是不是能够跑通,像这些细节问题,根据自己的实际情况设置即可!!!
1、ahb_top:作为系统的顶层模块,例化连接各个子模块
module ahb_top(
input hclk,//总线时钟
input hresetn,//复位信号
input enable,//top--->master,使能信号,决定是否进行读写操作
input [31:0] din,//top--->master,输入数据
input [31:0] addr,//top--->master,读写地址
input wr,//top--->master,读写控制信号
output [31:0] dout//master--->top,master读取到的slave的数据,输出到top方便查看
);
//--------------------------------------------------
// Connect wires
//--------------------------------------------------
wire [1:0] sel;
wire [31:0] haddr;
wire hwrite;
wire hready;
wire [31:0] hwdata;
wire [31:0] hrdata;
wire hreadyout;
wire hreadyout_1;
wire hreadyout_2;
wire hreadyout_3;
wire hreadyout_4;
wire [31:0]hrdata_1;
wire [31:0]hrdata_2;
wire [31:0]hrdata_3;
wire [31:0]hrdata_4;
//--------------------------------------------------
// AHB Master
//--------------------------------------------------
ahb_master master(
.hclk(hclk),
.hresetn(hresetn),
.enable(enable),
.din(din),
.addr(addr),
.wr(wr),
.hreadyout(hreadyout),
.hrdata(hrdata),
.haddr(haddr),
.hwrite(hwrite),
.hready(hready),
.hwdata(hwdata),
.dout(dout)
);
//--------------------------------------------------
// AHB Slave
//--------------------------------------------------
ahb_slave slave1(
.hclk(hclk),
.hresetn(hresetn),
.hsel(hsel_1),
.haddr(haddr),
.hwrite(hwrite),
.hready(hready),
.hwdata(hwdata),
.hreadyout(hreadyout_1),
.hrdata(hrdata_1)
);
ahb_slave slave2(
.hclk(hclk),
.hresetn(hresetn),
.hsel(hsel_2),
.haddr(haddr),
.hwrite(hwrite),
.hready(hready),
.hwdata(hwdata),
.hreadyout(hreadyout_2),
.hrdata(hrdata_2)
);
ahb_slave slave3(
.hclk(hclk),
.hresetn(hresetn),
.hsel(hsel_3),
.haddr(haddr),
.hwrite(hwrite),
.hready(hready),
.hwdata(hwdata),
.hreadyout(hreadyout_3),
.hrdata(hrdata_3)
);
ahb_slave slave4(
.hclk(hclk),
.hresetn(hresetn),
.hsel(hsel_4),
.haddr(haddr),
.hwrite(hwrite),
.hready(hready),
.hwdata(hwdata),
.hreadyout(hreadyout_4),
.hrdata(hrdata_4)
);
//--------------------------------------------------
// AHB Decoder
//--------------------------------------------------
decoder u_decoder (
.addr (addr[31:0]),
.hsel_1( hsel_1),
.hsel_2( hsel_2),
.hsel_3( hsel_3),
.hsel_4( hsel_4),
.sel(sel)
);
//--------------------------------------------------
// AHB Mux
//--------------------------------------------------
mux u_mux (
.hrdata_1 ( hrdata_1 [31:0] ),
.hrdata_2 ( hrdata_2 [31:0] ),
.hrdata_3 ( hrdata_3 [31:0] ),
.hrdata_4 ( hrdata_4 [31:0] ),
.hreadyout_1 ( hreadyout_1 ),
.hreadyout_2 ( hreadyout_2 ),
.hreadyout_3 ( hreadyout_3 ),
.hreadyout_4 ( hreadyout_4 ),
.sel ( sel [1:0] ),
.hrdata ( hrdata [31:0] ),
.hreadyout ( hreadyout )
);
endmodule
2、ahb_master:主机
module ahb_master(
/*
输入信号
*/
input hclk,//总线时钟
input hresetn,//总线复位
input enable,//top--->master,使能信号
input [31:0] din,//top--->master,输入数据
input [31:0] addr,//top--->master,32位总线地址,实际上最后还是要传给slave
input wr,//top--->master,控制此时是要进行读还是写,会影响hwrite的值
//slave--->mux--->master,1:Slave指出传输结束,0:Slave需延长传输周期
input hreadyout,
input [31:0] hrdata,//slave--->mux--->master,从slave读来的32位数据
/*
输出信号
*/
//master--->slave的32位总线地址,该信号也会传输到decoder,解析出选择了哪个从机
output reg [31:0] haddr,
output reg hwrite,//master--->slave 1:表示写传输 0:表示读传输
//master--->slave 非标准端口信号,表示master这边已经将控制信号和地址放到总线上了,从机可以采集了
output reg hready,
output reg [31:0] hwdata,//master--->slave 传给slave的32位写数据
output reg [31:0] dout//master--->top 将从slave读入的数据输出
);
//----------------------------------------------------
// The definitions for state machine
//----------------------------------------------------
reg [2:0] state, next_state;
/*
idle:空闲状态
control:准备状态,将地址和控制信号发送到总线上
write:写状态
read:读状态
*/
parameter idle = 0,control = 1, write = 2, read = 3;
//----------------------------------------------------
// The state machine
//----------------------------------------------------
/*
状态机第一段
*/
always @(posedge hclk, negedge hresetn) begin
if(!hresetn) begin
state <= idle;
end
else begin
state <= next_state;
end
end
/*
状态机第二段
*/
always @(*) begin
case(state)
idle: begin
if(enable == 1'b1)
next_state = control;
else
next_state = idle;
end
control:
if(wr == 1'b1)
next_state = write;
else
next_state = read;
write: begin
if(hreadyout == 1'b1) begin
next_state = idle;
end
else begin
next_state = write;
end
end
read: begin
if(hreadyout == 1'b1) begin
next_state = idle;
end
else begin
next_state = read;
end
end
default: begin
next_state = idle;
end
endcase
end
/*
状态机第三段
*/
always @(posedge hclk, negedge hresetn) begin
if(!hresetn) begin
haddr <= 32'h0000_0000;
hwrite <= 1'b0;
hready <= 1'b0;
hwdata <= 32'h0000_0000;
dout <= 32'h0000_0000;
end
else begin
case(state)
idle:begin
haddr <= 32'h0000_0000;
hwrite <= 1'b0;
hready <= 1'b0;
hwdata <= 32'h0000_0000;
dout <= 32'h0000_0000;
end
//读写开始之前向总线发送控制信号的准备阶段
//地址haddr、读写控制信号hwrite给送到了总线上
control: begin
haddr <= addr;
hwrite <= wr;
hready <= 1'b1;
hwdata <= 32'h0000_0000;;
dout <= 32'h0000_0000;;
end
//将数据放到数据线上
write: begin
haddr <= 32'h0000_0000;
hwrite <= 1'b0;
hready <= 1'b0;
hwdata <= din;
dout <= 32'h0000_0000;;
end
read: begin
haddr <= 32'h0000_0000;
hwrite <= 1'b0;
hready <= 1'b0;
hwdata <= 32'h0000_0000;;
dout <= hrdata;
end
default: begin
haddr <= 32'h0000_0000;
hwrite <= 1'b0;
hready <= 1'b0;
hwdata <= 32'h0000_0000;
dout <= 32'h0000_0000;
end
endcase
end
end
endmodule
3、ahb_slave:从机
module ahb_slave(
/*
输入数据
*/
input hclk,//总线时钟
input hresetn,//总线复位
input hsel,//master--->slave,从机选择信号
input [31:0] haddr,//master--->slave的32位总线地址
input hwrite,//master--->slave 1:表示写传输 0:表示读传输
//master--->slave 非标准端口信号,表示master那边已经将地址和控制信号放到总线上了,从机可以开始采集了
input hready,
input [31:0] hwdata,//master--->slave 传给slave的32位写数据
/*
输出数据
*/
//slave--->mux,1:Slave指出传输结束,0:Slave需延长传输周期
output reg hreadyout,
output reg [31:0] hrdata//slave--->mux,从slave读来的32位数据
);
//----------------------------------------------------------------------
// The definitions for intern registers for data storge
//----------------------------------------------------------------------
reg [31:0] mem [31:0];
reg [4:0] waddr;
reg [4:0] raddr;
//----------------------------------------------------------------------
// The definition for state machine
//----------------------------------------------------------------------
reg [1:0] state;
reg [1:0] next_state;
/*
idle:空闲状态,此时主机发送地址和控制信号
sample_addr_ctr:采集地址和控制信号
sample_data:采集数据,也即进行的是写操作
send_data:发送数据,也即主机进行的是读操作
*/
localparam idle = 2'b00, sample_addr_ctr = 2'b01, sample_data = 2'b10, send_data = 2'b11;
//----------------------------------------------------------------------
// The state machine
//----------------------------------------------------------------------
/*
状态机第一段
*/
always @(posedge hclk, negedge hresetn) begin
if(!hresetn) begin
state <= idle;
end
else begin
state <= next_state;
end
end
/*
状态机第二段
*/
always @(*) begin
case(state)
idle: begin
//如果该slave被选中,则进入采集地址和控制信号的状态,否则不工作直接保持idle状态
if(hsel == 1'b1) begin
next_state = sample_addr_ctr;
end
else begin
next_state = idle;
end
end
//此时可以采集如hwrite、hready这些控制信号以及地址了,
sample_addr_ctr: begin
//如果是写操作,那么下一状态进入write
if((hwrite == 1'b1) && (hready == 1'b1)) begin
next_state = sample_data;
end
//否则如果是读操作,下一状态进入read
else if((hwrite == 1'b0) && (hready == 1'b1)) begin
next_state = send_data;
end
else begin
next_state = sample_addr_ctr;
end
end
/*
采集数据状态,该状态结束后,因为单次传输模式下从机选择信号hsel一个周期就可以改变一次,
所以下面再判断一下是否被选中,如果选中,那么直接再次进入control,开启下一次读写判断,如此循环往复。
*/
sample_data: begin
next_state = idle;
end
/*
发送数据状态,该状态结束后,因为单次传输模式下从机选择信号hsel一个周期就可以改变一次,
所以下面再判断一下是否被选中,如果选中,那么直接再次进入control,开启下一次读写判断,如此循环往复。
*/
send_data: begin
next_state = idle;
end
default: begin
next_state = idle;
end
endcase
end
/*
状态机第三段
*/
always @(posedge hclk, negedge hresetn) begin
if(!hresetn) begin
hreadyout <= 1'b0;
hrdata <= 32'h0000_0000;
waddr <= 5'b0000_0;
raddr <= 5'b0000_0;
end
else begin
case(state)
idle: begin
hreadyout <= 1'b0;
hrdata <= 32'h0000_0000;
waddr <= 5'b0000_0;
raddr <= 5'b0000_0;
end
sample_addr_ctr: begin
hreadyout <= 1'b0;
hrdata <= 32'h0000_0000;
waddr <= haddr;
raddr <= haddr;
end
/*
从机获取主机的写数据,并将hreadyout信号置1
*/
sample_data: begin
hreadyout <= 1'b1;
mem[waddr[4:0]] <= hwdata;
end
/*
从机发送给主机的读数据,并将hreadyout信号置1
*/
send_data: begin
hreadyout <= 1'b1;
hrdata <= mem[raddr[4:0]];
end
default: begin
hreadyout <= 1'b0;
hrdata <= 32'h0000_0000;
waddr <= 5'b0000_0;
raddr <= 5'b0000_0;
end
endcase
end
end
endmodule
4、decoder:译码器,根据地址信号得到从机选择信号
module decoder(
input [31:0] addr,
output hsel_1,
output hsel_2,
output hsel_3,
output hsel_4,
output [1:0]sel
);
reg hsel_1_r;
reg hsel_2_r;
reg hsel_3_r;
reg hsel_4_r;
assign hsel_1 = hsel_1_r;
assign hsel_2 = hsel_2_r;
assign hsel_3 = hsel_3_r;
assign hsel_4 = hsel_4_r;
assign sel = addr[31:30];
always @(*) begin
case(sel)
2'b00: begin
hsel_1_r = 1'b1;
hsel_2_r = 1'b0;
hsel_3_r = 1'b0;
hsel_4_r = 1'b0;
end
2'b01: begin
hsel_1_r = 1'b0;
hsel_2_r = 1'b1;
hsel_3_r = 1'b0;
hsel_4_r = 1'b0;
end
2'b10: begin
hsel_1_r = 1'b0;
hsel_2_r = 1'b0;
hsel_3_r = 1'b1;
hsel_4_r = 1'b0;
end
2'b11: begin
hsel_1_r = 1'b0;
hsel_2_r = 1'b0;
hsel_3_r = 1'b0;
hsel_4_r = 1'b1;
end
default: begin
hsel_1_r = 1'b0;
hsel_2_r = 1'b0;
hsel_3_r = 1'b0;
hsel_4_r = 1'b0;
end
endcase
end
endmodule
5、mux:多路选择器,根据从机选择信号决定哪个从机的输出信号连接到主机
module mux(
input [31:0] hrdata_1,
input [31:0] hrdata_2,
input [31:0] hrdata_3,
input [31:0] hrdata_4,
input hreadyout_1,
input hreadyout_2,
input hreadyout_3,
input hreadyout_4,
input [1:0] sel,
output [31:0] hrdata,
output hreadyout
);
reg [31:0]hrdata_r;
reg hreadyout_r;
assign hrdata = hrdata_r;
assign hreadyout = hreadyout_r;
always @(*) begin
case(sel)
2'b00: begin
hrdata_r = hrdata_1;
hreadyout_r = hreadyout_1;
end
2'b01: begin
hrdata_r = hrdata_2;
hreadyout_r = hreadyout_2;
end
2'b10: begin
hrdata_r = hrdata_3;
hreadyout_r = hreadyout_3;
end
2'b11: begin
hrdata_r = hrdata_4;
hreadyout_r = hreadyout_4;
end
default: begin
hrdata_r = 32'h0000_0000;
hreadyout_r = 1'b0;
end
endcase
end
endmodule
四、设计仿真
`timescale 1ns/1ns
module tb_ahb_top();
reg hclk;
reg hresetn;
reg enable;
reg [31:0] din;
reg [31:0] addr;
reg wr;
wire [31:0] dout;
initial begin
hclk = 0;
hresetn = 0;
enable = 1'b0;
din = 32'd0;
addr = 32'd0;
wr = 1'b0;
#20 hresetn = 1;
write(32'h0000_0000,32'd1);
read(32'h0000_0000);
write(32'h4000_0004,32'd2);
read(32'h4000_0004);
write(32'h8000_0008,32'd3);
read(32'h8000_0008);
write(32'hc000_000c,32'd4);
read(32'hc000_000c);
read(32'h0000_0000);
read(32'h4000_0004);
read(32'h8000_0008);
read(32'hc000_000c);
end
task write( input [31:0] address, input [31:0] a);
begin
@(posedge hclk)
enable = 1'b1;
addr = address;
wr = 1'b1;
@(posedge hclk)
din = a;
@(posedge hclk)
@(posedge hclk)
@(posedge hclk)
enable = 1'b0;
wr = 1'b0;
end
endtask
task read(input [31:0] address);
begin
@(posedge hclk)
enable = 1'b1;
addr = address;
wr = 1'b0;
@(posedge hclk)
@(posedge hclk)
@(posedge hclk)
@(posedge hclk)
enable = 1'b0;
wr = 1'b0;
end
endtask
ahb_top dut(
.hclk(hclk),
.hresetn(hresetn),
.enable(enable),
.din(din),
.addr(addr),
.wr(wr),
.dout(dout)
);
always #2 hclk <= ~hclk;
endmodule
以上关于write task
和read task
中时钟周期数量的问题解释如下:
从给出addr和wr开始计算:
1、master将addr和wr分别赋值给haddr和hwrite需要两个周期 2
2、slave在haddr和hwrite放到总线上之前就已经进入采集状态等待着,并且要在hwrite和hready放到总线上之后的一个周期才能采集到它们 1
3、slave采集到hwrite和hready之后,下一个时钟周期进行读写操作,然后将hreadyout拉高 1
4、master在hreadyout拉高之后的一个周期才能采集到hreadyout,然后下一个周期进入idle,一次读写操作结束 1
所以算起来,一个write or read task至少需要2+1+1+1=5个hclk
该仿真程序功能是:依次往四个从机指定地址处写入1、2、3、4,每次写完之后紧接着就读出来。最后再依次从这些地址处依次读取四个从机,可以看出结果是正确的。