目录
一、实验目的
学习并掌握基本的AHB总线传输协议;使用Verilog HDL语言对AHB从机驱动模块进行设计,并满足正常的时序要求,使从机响应八位增量突发和十六位增量突发;掌握Modelsim仿真工具的使用。
二、实验工具及环境
1.Windows 10;
2.Modelsim 10.5;
3.Vivado 20.1;
4.Visual Studio Code。
三、实验内容及步骤
1.实验1.1 从机接口模块的设计
本实验需要设计一个AHB从机接口模块,提供AHB总线到从机Memory的接口,其框图如下图所示:
为了简化设计,不需要全面考虑所有可能的接口配置情况,只需要考虑HSIZE=3’b010即八位(一个字节)传输、HPROT=4’b0001即数据/预取指、HRESP为OKAY即传输成功的情况。
实验材料中给出的testbench验证从机在八位增量突发模式下的工作情况,通过阅读实验材料发现八位增量突发时序图如下所示:
①地址寄存:
在AHB总线传输协议中,进行一次数据传输的时候需要先发送地址再在下一个时钟周期发送或接收响应的数据,即先地址周期后数据周期,时序图如下图所示:
所以AHB从机在应当在前一个时钟周期读取地址,在后一个时钟周期采样数据。为了保证Memory在读写数据的时候地址的正确性,需要在前一个上升沿到来的时候寄存地址总线HADDR上的数据,因此使用一个地址寄存器address。核心代码如下所示:
②地址对齐:
由于AHB总线宽度为32位,而从机为8位寻址,要使得数据在一个八位增量突发的周期顺次写入相邻的地址,就需要使得地址总线寻址HADDR与Memory寻址addr相对齐。又由于32/8 = 4,因此HAB地址总线HADDR上传输的地址是以4为增量递增连续八个地址周期,在本次实验中,为32’h00000000到32’h0000001c;Memory地址则是以一为增量递增的,在本实验中为8’h00到8’h07。
对应的从机则需要将从地址总线HADDR上接收到的地址数据除4来进行这一个映射,但是除法器非常消耗硬件资源同时会造成速度下降,因此采用右移两位来替代除4的运算,代码为
③AHB_Slave状态机:
使用dot自动生成AHB_Slave状态机状态转移图如下图所示
根据需求使用了三段式Moore状态机来实现这个AHB总线的从机,命名为AHB_Slave,并根据从机未选中、从机选中进行写传输、从机选中进行读传输分为三个状态:IDLE、MEM_W、MEM_R,并使用独热码表示:
三段式状态机第一段为状态转移同步逻辑,通过时钟信号HCLK和异步复位信号HRESETn进行状态的转移:
三段式状态机的第二段为产生下一个状态的组合逻辑,根据HWRITE以及当前的状态确定下一个状态。根据HWRITE信号的定义,当HWRITE=1,状态跳转到MEM_W进行写操作;当HWRITE=0,状态跳转到MEM_R进行读操作。
三段式状态机的第三段为产生输出的组合逻辑,根据当前状态对输出信号HREADY、addr、dout、HRDATA、wr_en进行相应的输出。在IDLE状态时,由于从机未被选中,所以将HREADY置位为1表示从机空闲,addr置位为缺省状态8’h00;当从机处于MEM_W状态时需要对Memory进行操作,将寄存器address在地址周期寄存的地址送到addr,写数据总线HWDATA上的数据送到dout,在此时HRDATA没有使用置位为缺省32’d0;当从机处于MEM_R状态时需要对Memory进行读操作,将寄存器address在地址周期寄存的地址送到addr,将从Memory的din端口读出的数据送往读数据总线HRDATA,此时dout未使用置位为缺省的32’d0。具体代码如下所示:
详细代码见【附录】
2.实验1.2 十六位增量突发的仿真
本实验与实验1.1唯一的区别就在于实验1.1的testbench验证的是八位增量突发而实验1.2验证的是从机十六位增量突发,因此只需要改变testbench产生相应的激励就可以实现。
根据testbench.v代码可以发现,测试平台也是一个状态机,通过计数器cnt来控制对从机的激励的发生。对于八位增量突发来说,cnt从0计数到7产生连续八拍时钟周期的控制信号以达到模拟八位增量突发的效果。同理,只需要对cnt增加一位位宽使其在达到10000的时候不溢出,同时将所有4’h8替换为5’d16,再将HBURST由原先八位增量突发对应的101改成十六位增量突发对应的111即可完成对testbench的改写,仿真运行结果见第四部分。
四、实验结论及分析
实验1.1 八位增量突发的仿真结果
如上图所示,测试平台一共发起三次读写操作,其中第一次是对本实验所完成的从机模块进行写操作(other_slave信号为0、HWRITE信号为1)、第二次是对其他从机模块进行写操作(other_slave信号为1、HWRITE信号为1)、第三次则是对本实验所完成的从机模块进行读操作(other_slave信号为0、HWRITE信号为0),其仿真结果分析如下:
第一次传输:AHB_Slave的写操作
仿真结果如图所示,当HSEL和HWRITE都为高的时候从机进入写状态,在八个连续的总线周期过程中,HADDR和HWDATA发送地址和对应的数据。可以观察到HADDR从32’h00000000一直变化到32’h00000020,addr从8’h00一直变化到8’h08,这表明从机接口对Memory连续8个地址进行数据写操作,并使能wr_mem为高电平写操作,在addr变化的同时wr_data依次送入32’h00000002到32’h00000010一共8个数据,且地址相位在前,数据相位在后,正确的完成了八位增量突发的响应。其Memory List内容如下图所示:
通过观察发现,从8’h00到8’h10连续8个地址上对应32’h00000002到32’h0000000e八个数据,这表明AHB_Slave正确完成了八位增量突发的响应。
第二次传输:非AHB_Slave响应
仿真结果如图所示,此次传输HSEL为低电平表示未选中AHB_Slave从机。通过观察wr_data可以发现恒为32’h00000000,表示从机接口AHB_Slave并没有对Memory进行写操作,满足设计要求。
第三次传输:AHB_Slave的读操作
仿真结果如图所示,此次传输HSEL为高电平表示从机AHB_Slave被选中且HWRITE为低电平表示读数据传输。通过观察HADDR和HRDATA可以发现,在前一个周期(地址周期)HADDR发送读数据的地址后,AHB_Slave将HADDR做一个映射到Memory地址addr并使能wr_mem为低电平读操作,在addr变化的同时,Memory将数据从din放入读数据总线HRDATA上,由此可以看到在后一个周期(数据周期)上有对应的正确数据,因此AHB_Slave满足读操作的时序要求,功能完备。
实验1.2 十六位增量突发的仿真结果
第一次传输:AHB_Slave的写操作
仿真结果如图所示,当HSEL和HWRITE都为高的时候从机进入写状态,在十六个连续的总线周期过程中,HADDR和HWDATA发送地址和对应的数据。可以观察到addr从8’h00一直变化到8’h0f,这表明从机接口对Memory连续16个地址进行数据写操作,并使能wr_mem为高电平写操作,在addr变化的同时wr_data依次送入32’h00000002到32’h00000020一共16个数据,且地址相位在前,数据相位在后,正确的完成了十六位增量突发的响应。其Memory List内容如下图所示:
通过观察发现,从8’h00到8’h0f连续16个地址上对应32’h00000002到32’h00000020十六个数据,这表明AHB_Slave正确完成了十六位增量突发的响应。
第二次传输:非AHB_Slave响应
仿真结果如图所示,此次传输HSEL为低电平表示未选中AHB_Slave从机。通过观察wr_data可以发现恒为32’h00000000,表示从机接口AHB_Slave并没有对Memory进行写操作,满足设计要求。
第三次传输:AHB_Slave的读操作
仿真结果如图所示,此次传输HSEL为高电平表示从机AHB_Slave被选中且HWRITE为低电平表示读数据传输。通过观察HADDR和HRDATA可以发现,在前一个周期(地址周期)HADDR发送读数据的地址后,AHB_Slave将HADDR做一个映射到Memory地址addr并使能wr_mem为低电平读操作,在addr变化的同时,Memory将数据从din放入读数据总线HRDATA上,由此可以看到在后一个周期(数据周期)上有对应的正确数据,因此AHB_Slave满足读操作的时序要求,功能完备。
【附录】
- AHB_Slave.v
module AHB_Slave(
input HCLK,
input HRESETn,
input HSEL,//从机选择
input [3:0]HPROT,//保护控制 恒为4'b0001
input [2:0]HSIZE,//传输大小 恒为3'b010
input HWRITE,//传输方向:1写0读
input [2:0]HBURST,//突发类型
input [1:0]HTRANS,//传输类型
input [31:0]HADDR,//地址总线
input [31:0]HWDATA,//写数据总线
output reg [31:0]HRDATA,//读数据总线
output reg HREADY,//传输完成标志
input [1:0]HRESP,//传输相应 恒为OKEY 2'b00
output reg [7:0]addr,//AHB_Salve地址输出
output reg wr_en,//Mem写:1写0读
output reg [31:0]dout,//AHB_Salve数据输出
input [31:0]din//AHB_Salve数据输入
);
//状态寄存器定义
reg [2:0]cstate,nstate;
//寄存地址
reg [31:0]address;
//状态机状态定义
parameter IDLE = 3'b100;
parameter MEM_W = 3'b010;
parameter MEM_R = 3'b001;
//地址寄存逻辑
always @(posedge HCLK or negedge HRESETn) begin
if(!HRESETn)begin
address <= 8'd0;
end
else begin
address <= HADDR[9:2];//地址映射
end
end
//状态转移同步逻辑
always@(posedge HCLK or negedge HRESETn)begin
if(!HRESETn)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//产生下一个状态组合逻辑
always@(*)begin
if(!HRESETn)begin//从机复位
nstate = IDLE;
end
else if(HSEL)begin//从机被选中
// nstate = HWRITE?MEM_W:MEM_R;
if(HWRITE)
nstate = MEM_W;
else
nstate = MEM_R;
end
else begin//从机未选中
nstate = nstate;
end
end
//产生输出组合逻辑
always@(*)begin
case(cstate)
IDLE:begin
HREADY = 1'b1;
addr = 8'h00;
end
MEM_W:begin
HREADY = 1'b1;
wr_en = 1'b1;
addr = address;
dout = HWDATA;
HRDATA = 32'd0;
end
MEM_R:begin
HREADY = 1'b1;
wr_en = 1'b0;
addr = address;
dout = 32'd0;
HRDATA = din;
end
endcase
end
endmodule
- 十六位增量突发的testbench.v
`timescale 1ns/1ns
module testbench1;
reg HCLK;
reg HRESETn;
reg write;
reg read;
reg other_slave;
initial begin
HCLK = 1;
HRESETn = 0;
write = 0;
read = 0;
other_slave = 0;
#100 HRESETn = 1;
#10 write = 1;
#20 write = 0;
#1000 other_slave = 1;
write = 1;
#20 write = 0;
#1000 other_slave = 0;
read = 1;
#20 read = 0;
end
always #10 HCLK = ~HCLK;
wire HSEL = other_slave ? 1'b0 : 1'b1;
wire [3:0] HPROT = 4'b0001;
wire [2:0] HSIZE = 3'b010;
reg HWRITE;
wire [2:0] HBURST = 3'b111;/*****************/
reg [1:0] HTRANS;
reg [31:0] HADDR;
reg [31:0] HWDATA;
wire [31:0] HRDATA;
wire HREADY;
wire [1:0] HRESP;
wire [7:0] addr;
wire wr_mem;
wire [31:0] wr_data;
wire [31:0] rd_data;
localparam IDLE = 3'b001,
WRITE = 3'b010,
READ = 3'b100;
localparam HTRANS_IDLE = 2'b00,
HTRANS_BUSY = 2'b01,
HTRANS_NONSEQ = 2'b10,
HTRANS_SEQ = 2'b11;
reg [2:0] cstate;
reg [2:0] nstate;
reg [4:0] cnt;/*****************/
always @ (posedge HCLK or negedge HRESETn) begin
if(!HRESETn)
cstate <= IDLE;
else
cstate <= nstate;
end
always @ (*) begin
nstate = IDLE;
case(cstate)
IDLE : begin
if(write)
nstate = WRITE;
if(read)
nstate = READ;
end
WRITE : begin
if(HREADY && cnt == 5'd16)
nstate = IDLE;
else
nstate = WRITE;
end
READ : begin
if(HREADY && cnt == 5'd16)
nstate = IDLE;
else
nstate = READ;
end
endcase
end
always @ (posedge HCLK or negedge HRESETn) begin
if(!HRESETn)
cnt <= 5'h0;
else begin
case(cstate)
IDLE : begin
cnt <= 5'h0;
end
WRITE : begin
if(HREADY && cnt < 5'd16)
cnt <= cnt + 1'h1;
if(HREADY && cnt == 5'd16)
cnt <= 5'h0;
end
READ : begin
if(HREADY && cnt < 5'd16)
cnt <= cnt + 1'h1;
if(HREADY && cnt == 5'd16)
cnt <= 5'h0;
end
endcase
end
end
always @ (*) begin
HTRANS = HTRANS_IDLE; // 2'b00
case(cstate)
WRITE : begin
if(cnt == 5'h0)
HTRANS = HTRANS_NONSEQ; //2'b10
else if(cnt == 5'd16)
HTRANS = HTRANS_IDLE;
else
HTRANS = HTRANS_SEQ; //2'b11
end
READ : begin
if(cnt == 5'h0)
HTRANS = HTRANS_NONSEQ;
else if(cnt == 5'd16)
HTRANS = HTRANS_IDLE;
else
HTRANS = HTRANS_SEQ;
end
endcase
end
always @ (*) begin
HWRITE = 1'b0;
case(cstate)
WRITE : begin
if(cnt == 5'd16)
HWRITE = 1'b0;
else
HWRITE = 1'b1;
end
READ : begin
HWRITE = 1'b0;
end
endcase
end
always @ (posedge HCLK or negedge HRESETn) begin
if(!HRESETn)
HADDR <= 32'h0;
else begin
case(cstate)
IDLE : begin
HADDR <= 32'h0;
end
WRITE : begin
if(HREADY && cnt < 5'd16)
HADDR <= HADDR + 32'h4;
if(HREADY && cnt == 5'd16)
HADDR <= 32'h0;
end
READ : begin
if(HREADY && cnt < 5'd16)
HADDR <= HADDR + 32'h4;
if(HREADY && cnt == 5'd16)
HADDR <= 32'h0;
end
endcase
end
end
always @ (posedge HCLK or negedge HRESETn) begin
if(!HRESETn)
HWDATA <= 32'h0;
else begin
case(cstate)
IDLE : begin
HWDATA <= 32'h0;
end
WRITE : begin
if(HREADY && !HSEL && cnt < 5'd16)
HWDATA <= HWDATA + 32'h1;
if(HREADY && HSEL && cnt < 5'd16)
HWDATA <= HWDATA + 32'h2;
if(HREADY && cnt == 5'd16)
HWDATA <= 32'h0;
end
endcase
end
end
AHB_Slave ahb_slave (
.HCLK (HCLK),
.HRESETn (HRESETn),
.HSEL (HSEL),
.HPROT (HPROT),
.HSIZE (HSIZE),
.HWRITE (HWRITE),
.HBURST (HBURST),
.HTRANS (HTRANS),
.HADDR (HADDR),
.HWDATA (HWDATA),
.HRDATA (HRDATA),
.HREADY (HREADY),
.HRESP (HRESP),
.addr (addr),
.wr_en (wr_mem),
.dout (wr_data),
.din (rd_data)
);
RAM RAM (
.clka (HCLK),
.wea (wr_mem), //高写低读
.addra (addr),
.dina (wr_data),
.douta (rd_data)
);
endmodule