- 第一周学习内容
利用寒假时间学习一些数字ic相关的内容,每周记录一下。
基本都是现有资料文章的整理,最后标明了出处。
欢迎大家指点、交流。
文章目录
学习路径
- 百科上搜索了解AMBA的是什么
- eetop上下载了arm公司官方的 AMBA2.0总线规范
- csdn上moshanghongfeng大佬的翻译的AMBA总线规范也很好,有些部分有中英对照看着比较清楚。链接: https://blog.csdn.net/moshanghongfeng/article/details/108931201
- 寻找代码例程,很少有专门这方面的仓库,最后在知乎上“托管小弟”的专栏“AMBA协议详解与Verilog实现”找到了,本文是复现的他的代码,一些地方进行了改动。注释基本都是我自己添的,可能有理解错误的地方。
提示:以下是本篇文章正文内容,下面案例可供参考
一、AMBA是什么?
AMBA(Advanced Microcontroller Bus Architecture)总线协议是一种面向高性能嵌入式微控制器设计的片上联接标准。
AMBA总线(截至AMBA2.0)确定了三种总线标准:
AHB:(Advanced High-performance Bus)高级高性能总线 用于高性能、高时钟频率的系统模块。
ASB:(Advanced System Bus)高级系统总线 用于高性能的系统模块之间
APB:(Advanced Peripheral Bus)高级外设总线 用于低功耗外设
二、APB总线
总概
APB具备以下特性:
(1)低功耗;
(2)接口协议简单;
(3)总线传输使用时钟上升沿进行,便于时序分析;
(4)应用广泛,支持多种外设;
(5)所有的APB模块均是APB从机。
APB 桥是 AMBA APB 中的唯一总线主机。另外,APB 桥也是高级系统总线中的一个
从机。
所以我从最简单的APB总线学起
以下状态图表示了APB的三个状态,即外设总线的活动性
写传输时序:(注意看虚线的位置)
写传输开始于T2时刻,在改时钟上升沿时刻,地址、写信号、PSEL、写数据信号同时发生变化,T2时钟,即传输的第一个时钟被称为SETUP周期。在下个时钟上升沿T3,PENABLE信号拉高,表示ENABLE周期,在该周期内,数据、地址以及控制信号都必须保持有效。整个写传输在这个周期结束时完成。
读传输时序:
地址、写、选择和选通信号的时序都和写传输一样。在读传输的情况下,从机必须在
EANBLE 周期提供数据。数据在 ENABLE 周期末尾的时钟上升沿被采样。
一个通过apb总线连接的sram外设实现:
`timescale 1ns / 1ps
module apb_sram #(
parameter SIZE_IN_BYTES = 1024
)
(
//----------------------------------
// IO Declarations
//----------------------------------
input PRESETn,
/*APB 总线复位信号为低有效并且通常将该信号直接连接到系统总线复位信号。*/
input PCLK,
//PCLK 的上升沿用作所有 APB 传输的时基
input PSEL,
/*来自二级译码器的信号,从外设总线桥接单元内到每个外设总线从机 x。
该信号表示从机设备被选中并且要求一次数据传输。每个总线从机都有
一个 PSELx 信号*/
input [31:0] PADDR,
//这是 APB 地址总线,可高达 32 位宽度并由外设总线桥接单元驱动。
input PENABLE,
/*这个选通信号用来给外设总线上的所有访问提供时间。使能信号用来表
示一次 APB 传输的第二个周期。PENABLE 的上升沿出现在 APB 传输
的中间。*/
input PWRITE,
//该信号为高表示一次 APB 写访问而为低表示一次读访问。
input [31:0] PWDATA,
/*读数据总线由被选中的从机在读周期(PWRITE 为低)期间驱动。读数
据总线可达到 32 位宽度。*/
output reg [31:0] PRDATA
/*读数据总线由被选中的从机在读周期(PWRITE 为低)期间驱动。读数
据总线可达到 32 位宽度。*/
);
//----------------------------------
// Local Parameter Declarations
//----------------------------------
localparam A_WIDTH = clogb2(SIZE_IN_BYTES);
//----------------------------------
// Variable Declarations
//----------------------------------
reg [31:0] mem[0:SIZE_IN_BYTES/4-1];
//AMBA2.0最多可以传输高达 32 位宽度的地址,这里是地址只有30位,所以除以四
wire wren;
wire rden;
wire [A_WIDTH-1:2] addr;
//----------------------------------
// Function Declarations
//----------------------------------
function integer clogb2;
//判断value-1用二进制表示有几位
//也就是value取2的对数再向上取整
//原理:二进制n位数最大是2的n次方减一,所以先减一,再判断他有几位,就求出了n的上整数
//类似于C艹里面的31 - __builtin_clz(x);
input [31:0] value;
reg [31:0] tmp;
reg [31:0] rt;
begin
tmp = value - 1;
for (rt = 0; tmp > 0; rt = rt + 1)
tmp = tmp >> 1;
clogb2 = rt;
end
endfunction
//----------------------------------
// Start of Main Code
//----------------------------------
// Create read and write enable signals using APB control signals
assign wren = PWRITE && PENABLE && PSEL; // Enable Period 写使能
assign rden = ~PWRITE && ~PENABLE && PSEL; // Setup Period 读使能
/*写时序中, 从机在ENABLE周期结束的时钟上升沿里从总线采样数据,
因此在ENABLE周期中用PENABLE作为输入进行判断即可;
而读时序中, 从机要在SETUP周期结束时的上升沿里就把数据送到总线,
主机在ENABLE周期结束的上升沿里取数据,
这就要求从机用SETUP周期里的PENABLE(此时为1'b0)来作为输入, 判断是否需要送数据.*/
//详见AMBA 总线规范 P101
assign addr = PADDR[A_WIDTH-1:2];
// Write mem
always @(posedge PCLK)
begin
if (wren)
mem[addr] <= PWDATA;
end
// Read mem
always @(posedge PCLK)
begin
if (rden)
PRDATA <= mem[addr];
else
PRDATA <= 'h0;
end
endmodule
我的理解都写在注释里了,就不讲了。
验证平台:
`timescale 1ns / 1ps
`ifndef CLK_FREQ
`define CLK_FREQ 50_000_000 //50MHz
`endif
//使用 ifndef 、define 和endif的目的:为了防止同一个文件在编译时被重复编译,引起多重定义的问题。
//ifndef 的含义:即 “if not defined”,也就是说,当文件编译到这一行,如果这个文件还没有被编译过,也就是首次编译,就会执行后续的 `define xxx这句话,把后续的代码定义一次。反之,则不会再重复编译。
//ifdef 的含义:即"if defined",与 ifndef 的作用相反,如果已经编译过,那么则继续执行后面的代码。
//enif 的含义:出现 ifndef 或者 ifdef 作为开头,程序块的末尾就需要有 endif 作为结束的标识。
module apb_sram_tb();
//----------------------------------
// Local Parameter Declarations
//----------------------------------
parameter SIZE_IN_BYTES = 1024;
localparam CLK_FREQ = `CLK_FREQ;
localparam CLK_PERIOD_HALF = 1e9/CLK_FREQ/2;//1s==1e9ns
//----------------------------------
// Variable Declarations
//----------------------------------
reg PRESETn = 1'b0;
reg PCLK = 1'b0;
reg PSEL;
reg [31:0] PADDR;
reg PENABLE;
reg PWRITE;
reg [31:0] PWDATA;
wire [31:0] PRDATA;
reg [31:0] reposit[0:1023];//这是模拟给sram传数据的仓库
//----------------------------------
// Start of Main Code
//----------------------------------
apb_sram #(
.SIZE_IN_BYTES (SIZE_IN_BYTES)
)
u_apb_sram (
.PRESETn (PRESETn),
.PCLK (PCLK),
.PSEL (PSEL),
.PADDR (PADDR),
.PENABLE (PENABLE),
.PWRITE (PWRITE),
.PWDATA (PWDATA),
.PRDATA (PRDATA)
);
// generate PCLK
always #CLK_PERIOD_HALF
begin
PCLK <= ~PCLK;
end
// generate PRESETn
initial begin
PRESETn <= 1'b0;
repeat(5) @(posedge PCLK);//复位5个周期后再把PRESETn置1。敏感量列表后面直接加空是代表空操作。
PRESETn <= 1'b1;//如果是#加时间,有可能会吞波形。
end
// test memory
initial begin
PSEL = 1'b0;
PADDR = ~32'h0;
PENABLE = 1'b0;
PWRITE = 1'b0;
PWDATA = 32'hffff_ffff;
wait(PRESETn == 1'b0);
wait(PRESETn == 1'b1);
repeat(3) @(posedge PCLK);
memory_test(0, SIZE_IN_BYTES/4-1);
repeat(5) @(posedge PCLK);
$finish(2);
/*
0:不输出任何信息;
1:输出当前仿真时刻和位置;
2:输出当前仿真时刻、位置和仿真过程中所用的memory及CPU时间的统计。
当$finish后面不带参数时,则默认参数为1。
*/
end
// memory test task
task memory_test;
// starting address
input [31:0] start;
// ending address, inclusive
input [31:0] finish;
reg [31:0] dataW;
reg [31:0] dataR;
integer a;
integer b;
integer err;
begin
err = 0;
// read-after-write test
for (a = start; a <= finish; a = a + 1) begin
dataW = $random;
apb_write(4*a, dataW);
apb_read (4*a, dataR);
if (dataR !== dataW) begin//使用不全等,防止有高阻态或者不定态导致不好判断
err = err + 1;
$display($time,,"%m Read after Write error at A:0x%08x D:0x%x, but 0x%x expected", a, dataR, dataW);
end
end
if (err == 0)
$display($time,,"%m Read after Write 0x%x-%x test OK", start, finish);
err = 0;
// read_all-after-write_all test
for (a = start; a <= finish; a = a + 1) begin
b = a - start;
reposit[b] = $random;
apb_write(4*a, reposit[b]);
end
for (a = start; a <= finish; a = a + 1) begin
b = a - start;
apb_read(4*a, dataR);
if (dataR !== reposit[b]) begin
err = err + 1;
$display("***%m***: Read after Write error at A:0x%08x D:0x%x, but 0x%x expected,happend at %d", a, dataR, dataW,$time);
end
end
if (err == 0)
$display($time,"%m Read all after Write all 0x%x-%x test OK", start, finish);
end
endtask
// APB write task
task apb_write;
input [31:0] addr;
input [31:0] data;
begin
@(posedge PCLK);
PADDR <= #1 addr;
PWRITE <= #1 1'b1;
PSEL <= #1 1'b1;
PWDATA <= #1 data;
@(posedge PCLK);
PENABLE <= #1 1'b1;
@(posedge PCLK);
PSEL <= #1 1'b0;
PENABLE <= #1 1'b0;
end
endtask
// APB read task
task apb_read;
input [31:0] addr;
output [31:0] data;
begin
@(posedge PCLK);
PADDR <= #1 addr;
PWRITE <= #1 1'b0;
PSEL <= #1 1'b1;
@(posedge PCLK);
PENABLE <= #1 1'b1;
@(posedge PCLK);
PSEL <= #1 1'b0;
PENABLE <= #1 1'b0;
data = PRDATA; // it should be blocking 我猜测是因为阻塞赋值可以保证这一步数据读取完毕了才会进行后续操作
end
endtask
`ifdef VCS
initial begin
$fsdbDumpfile("apb_sram_tb.fsdb");
$fsdbDumpvars;
end
initial begin
`ifdef DUMP_VPD
$vcdpluson();
`endif
end
`endif
endmodule
仿真波形
写传输:两蓝线之间是setup周期,sel信号将该sram外设选中,写地址、写数据在此周期准备好。蓝黄线之间是enable周期,此周期内PENABLE信号被拉高,写地址、写数据此周期保持有效。数据在黄线时刻传输。
读传输:两蓝线之间是setup周期,sel信号将该sram外设选中,读地址、读数据在此周期准备好。蓝黄线之间是enable周期,此周期内PENABLE信号被拉高,读地址、读数据此周期保持有效。从机要在SETUP周期结束时的上升沿里就把数据送到总线, 主机在ENABLE周期结束的上升沿里取数据。
总结与疑问
- 为什么apb读数据要在enable周期结束时完成?在enable周期开始时数据明明就准备好了。或许其实就是在enable周期开始时传输的?因为我将代码
assign wren = PWRITE && PENABLE && PSEL; // Enable Period 写使能
assign rden = ~PWRITE && ~PENABLE && PSEL; // Setup Period 读使能
改成:
assign wren = PWRITE && ~PENABLE && PSEL; // Enable Period 写使能
assign rden = ~PWRITE && ~PENABLE && PSEL; // Setup Period 读使能
功能同样正确
- 为什么要加个penable信号,这信号完全是根据时钟赋值的,也就是说其实并不能体现setup周期的数据准备好了没有,那为何不直接根据时钟第一周期setup第二周期enable?我想到的原因或许是这信号是主机给的,无论apb接多少个从机,这penable占用的资源只有一个(无论是综合出buffer还是counter),从机也更纯粹便于调试。当然也可能就是协议的统一标准,为了规范,没有什么特别的原因。
三、AHB总线
简介
典型的 AMBA AHB 系统设计包含以下的成分:
- AHB 主机 总线主机能够通过提供地址和控制信息发起读写操作。任何时候只允许一
个总线主机处于有效状态并能使用总线。 - AHB 从机 总线从机在给定的地址空间范围内响应读写操作。总线从机将成功、失败
或者等待数据传输的信号返回给有效的主机。 - AHB 仲裁器 总线仲裁器确保每次只有一个总线主机被允许发起数据传输。即使仲裁协
议已经固定,任何一种仲裁算法,比如最高优先级或者公平访问都能够根据应用要求而得到执行。
部分仲裁器信号名:
注意:
AHB 必须只包含一个仲裁器,尽管在单总线主机系统中这显得并不重要。
AHB 译码器 AHB 译码器用来对每次传输进行地址译码并且在传输中包含一个从机选
择信号。
所有 AHB 执行都必须仅要求有一个中央译码器。
传输类型
传输流程
- master发起一个请求给仲裁器
- arbiter运行某个主设备控制总线
- master驱动地址和控制信号
- 仅选中的从设备响应地址/控制信号
- 传输完成后slave拉高HREADY信号,总线传输完成
两个阶段
■地址周期,只有一个cycle
■数据周期,由HREADY信号决定需要几个cycle流水线传送
先是地址周期,然后是数据周期
一次无须等待的简单传输只需要两拍
可以使用流水线传输
突发传输
下图是一次突发传输的示例:
T2时主机busy没有准备好传输,HADDR必须保持直到busy状态取消;T5时,HREADY未被拉高,从机没有准备好,data也必须等待HREADY被拉高再开始传输。
下图是一次从机retry的示例
response中split和retry的区别
主要区别在于仲裁的方式
. RETRY: arbiter会继续使用通常的优先级
. SPLIT: arbiter会调整优先级方案以便其他请求总线的主设备可以访问总线
总线主设备应该用同样的方式处理RETRY响应和SPLIT响应
虚拟主机和默认主机
地址译码
地址译码AHB总线中,会使用一个中央译码器来产生HSELx信号,用于选择从机,选择信号由地址高位信号组合译码产生。建议使用简单的译码方案来保证高速操作。从机只能在HREADY信号为高时,采样地址和控制信号以及HSELx,当HREADY信号为高时,表示传输完成。AHB有几个特别的规定:
(1)每个从机至少都有 1KB 的内存空间。
(2)每个主机每次存取的空间不可超过 1KB。
(3)如果在 NONSEQ 或 SEQ 型态下存取到不存在的地址时,会有一个预设的从机发出 ERROR 的响应信号。
(4)如果在 IDLE 或 BUSY 型态下存取到不存在的地址时,会有 OKAY 的响应信号。
(5)预设的从机是中央译码器的一部分。
(6)根据系统设计,使用地址的高位来产生从机选择信号。
(7)地址的低位送给从机以寻址其的内部存储器或缓存器
互联
AHB信号以字母H作为前缀,下表为部分信号名称:
主设备接口
从设备接口
一个ahpSram的实现
code
ahbSRAM接口定义:
module ahb_sram_if #(
parameter AHB_DWIDTH = 32,
parameter AHB_AWIDTH = 32,
parameter ADD_WIDTH = 11,
parameter SYNC_RESET = 0
)
(
//----------------------------------
// IO Declarations
//----------------------------------
// Inputs
input HCLK,
input HRESETN,
(* DONT_TOUCH= "TRUE" *) input HSEL,
input HREADYIN,
input [1:0] HTRANS,
input [2:0] HBURST,
input [2:0] HSIZE,
input [ADD_WIDTH-1:0] HADDR,
input [AHB_DWIDTH-1:0] HWDATA,
input HWRITE,
input sramahb_ack,
input [AHB_DWIDTH-1:0] sramahb_rdata,
// Outputs
output HREADYOUT,
output [1:0] HRESP,
output reg [AHB_DWIDTH-1:0] HRDATA,
output ahbsram_req,
output ahbsram_write,
output [AHB_AWIDTH-1:0] ahbsram_wdata,
output [2:0] ahbsram_size,
output [ADD_WIDTH-1:0] ahbsram_addr
);
//----------------------------------
// Local Parameter Declarations
//----------------------------------
// State Machine parameters
localparam IDLE = 2'b00;
localparam AHB_WR = 2'b01;
localparam AHB_RD = 2'b10;
parameter RESP_OKAY = 2'b00;
parameter RESP_ERROR = 2'b01;
// AHB HTRANS definition
parameter TRN_IDLE = 2'b00;
parameter TRN_BUSY = 2'b01;
parameter TRN_SEQ = 2'b11;
parameter TRN_NONSEQ = 2'b10;
parameter SINGLE = 3'b000;
parameter INCR = 3'b001;
parameter WRAP4 = 3'b010;
parameter INCR4 = 3'b011;
parameter WRAP8 = 3'b100;
parameter INCR8 = 3'b101;
parameter WRAP16 = 3'b110;
parameter INCR16 = 3'b111;
//----------------------------------
// Variable Declarations
//----------------------------------
reg [1:0] HTRANS_d;
reg [2:0] HBURST_d;
reg [2:0] HSIZE_d;
reg [ADD_WIDTH-1:0] HADDR_d;
reg [AHB_DWIDTH-1:0] HWDATA_d;
reg HWRITE_d;
reg HSEL_d;
reg HREADYIN_d;
reg [1:0] ahbcurr_state;
reg [1:0] ahbnext_state;
reg latchahbcmd;
reg ahbsram_req_int;
reg ahbsram_req_d1;
reg [AHB_DWIDTH-1:0] HWDATA_cal;
reg [4:0] burst_count;
reg [4:0] burst_count_reg;
reg [4:0] count;
wire aresetn;
wire sresetn;
//----------------------------------
// Start of Main Code
//----------------------------------
assign aresetn = (SYNC_RESET==1) ? 1'b1 : HRESETN;
assign sresetn = (SYNC_RESET==1) ? HRESETN : 1'b1;
// Generation of valid AHB Command which triggers the AHB Slave State Machine
assign validahbcmd = HREADYIN & HSEL & (HTRANS == TRN_NONSEQ);
// Generation of HRESP
assign HRESP = RESP_OKAY;
always @(*)
begin
HWDATA_cal = HWDATA;
end
// Latch all the AHB signals
always @(posedge HCLK or negedge aresetn)
begin
if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin
HADDR_d <= {20{1'b0}};
HWDATA_d <= {32{1'b0}};
HTRANS_d <= 2'b00;
HSIZE_d <= 2'b00;
HBURST_d <= 3'b000;
HWRITE_d <= 1'b0;
HSEL_d <= 1'b0;
HREADYIN_d <= 1'b0;
end
else if (HREADYIN == 1'b1 & HSEL == 1'b1 & HREADYOUT == 1'b1) begin
HADDR_d <= HADDR;
HTRANS_d <= HTRANS;
HSIZE_d <= HSIZE;
HBURST_d <= HBURST;
HWRITE_d <= HWRITE;
HWDATA_d <= HWDATA_cal;
HSEL_d <= HSEL;
HREADYIN_d <= HREADYIN;
end
end
// Current State generation
always @(posedge HCLK or negedge aresetn)
begin
if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin
ahbcurr_state <= IDLE;
end
else begin
ahbcurr_state <= ahbnext_state;
end
end
// Next State and output decoder logic
always @(*)
begin
latchahbcmd = 1'b0;
ahbsram_req_int = 1'b0;
ahbnext_state = ahbcurr_state;
case (ahbcurr_state)
IDLE : begin
if (HREADYIN == 1'b1 && HSEL == 1'b1 && ((HTRANS == TRN_NONSEQ) || HTRANS == TRN_SEQ)) begin
latchahbcmd = 1'b1;
if (HWRITE == 1'b1) begin
ahbnext_state = AHB_WR;
end
else begin
ahbnext_state = AHB_RD;
end
end
else begin
ahbnext_state = IDLE;
end
end
AHB_WR : begin
latchahbcmd = 1'b0;
ahbsram_req_int = 1'b1;
if (sramahb_ack == 1'b1) begin
if (count == burst_count_reg) begin
ahbnext_state = IDLE;
end
else begin
ahbsram_req_int = 1'b0;
end
end
end
AHB_RD : begin
latchahbcmd = 1'b0;
ahbsram_req_int = 1'b1;
if (sramahb_ack == 1'b1) begin
ahbnext_state = IDLE;
end
end
default : begin
ahbnext_state = IDLE;
end
endcase
end
// LOGIC FOR BURST COUNT
always @(*)
begin
burst_count = burst_count_reg;
if (HSEL == 1'b1 && HTRANS == TRN_NONSEQ && HREADYIN == 1'b1 && HREADYOUT == 1'b1) begin
case (HBURST)
SINGLE :
burst_count = 5'b00001;
WRAP4,INCR4 :
burst_count = 5'b00100;
WRAP8,INCR8 :
burst_count = 5'b01000;
WRAP16,INCR16 :
burst_count = 5'b10000;
default :
burst_count = 4'b0001;
endcase
end
end
always @(posedge HCLK or negedge aresetn)
begin
if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin
burst_count_reg <= 'h0;
end
else begin
burst_count_reg <= burst_count;
end
end
always @(posedge HCLK or negedge aresetn)
begin
if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin
count <= 5'h0;
end
else begin
if (count == burst_count_reg) begin
count <= 5'h0;
end
else if (ahbsram_req == 1'b1) begin
count <= count + 1'b1;
end
else begin
count <= count;
end
end
end
assign HREADYOUT = !ahbsram_req_int;
// Generation of signals required for SRAM
assign ahbsram_write = ahbsram_req ? HWRITE_d : 1'b0;
assign ahbsram_wdata = HWDATA;
assign ahbsram_addr = ahbsram_req ? HADDR_d : HADDR_d;
assign ahbsram_size = ahbsram_req ? HSIZE_d : HSIZE_d;
always @(posedge HCLK or negedge aresetn)
begin
if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin
ahbsram_req_d1 <= 1'b0;
end
else begin
ahbsram_req_d1 <= ahbsram_req_int;
end
end
// Generate the request to the SRAM contol logic when there is AHB read or write request
assign ahbsram_req = ahbsram_req_int & !ahbsram_req_d1;
// HRDATA generation
always @(*)
begin
if (HREADYOUT && HREADYIN) begin
HRDATA = sramahb_rdata;
end
else begin
HRDATA = sramahb_rdata;
end
end
endmodule
sram控制信号接口定义
`timescale 1ns / 1ps
module sram_ctrl_if #(
parameter AHB_DWIDTH = 32,
parameter ADD_WIDTH = 11,
parameter SYNC_RESET = 0
)
(
//----------------------------------
// IO Declarations
//----------------------------------
// Inputs
input HCLK,
input HRESETN,
input ahbsram_req,
input ahbsram_write,
input [2:0] ahbsram_size,
input [ADD_WIDTH-1:0] ahbsram_addr,
input [AHB_DWIDTH-1:0] ahbsram_wdata,
// Outputs
output sramahb_ack,
output reg [AHB_DWIDTH-1: 0] sramahb_rdata
);
//----------------------------------
// Local Parameter Declarations
//----------------------------------
// State Machine parameters
localparam S_IDLE = 2'b00;
localparam S_WR = 2'b01;
localparam S_RD = 2'b10;
//----------------------------------
// Variable Declarations
//----------------------------------
reg [3:0] sram_wen_mem;
reg [1:0] sramcurr_state;
reg [1:0] sramnext_state;
reg sram_wen;
reg sram_ren;
reg sramahb_ack_int;
reg sram_ren_d;
reg sram_done;
wire [AHB_DWIDTH-1:0] ram_rdata;
wire aresetn;
wire sresetn;
//----------------------------------
// Start of Main Code
//----------------------------------
assign aresetn = (SYNC_RESET == 1) ? 1'b1 : HRESETN;
assign sresetn = (SYNC_RESET == 1) ? HRESETN : 1'b1;
// Current State generation
always @(posedge HCLK or negedge aresetn)
begin
if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin
sramcurr_state <= S_IDLE;
end
else begin
sramcurr_state <= sramnext_state;
end
end
// Next State and output decoder logic
always @(*)
begin
sramahb_ack_int = 1'b0;
sram_wen = 1'b0;
sram_ren = 1'b0;
sramnext_state = sramcurr_state;
case (sramcurr_state)
S_IDLE : begin
if (ahbsram_req == 1'b1) begin
if (ahbsram_write == 1'b1) begin
sramnext_state = S_WR;
sram_wen = 1'b1;
end
else begin
sram_ren = 1'b1;
sramnext_state = S_RD;
end
end
end
S_WR : begin
if (sram_done == 1'b1) begin
sramnext_state = S_IDLE;
sramahb_ack_int = 1'b1;
end
end
S_RD : begin
if (sram_done == 1'b1) begin
sramnext_state = S_IDLE;
sramahb_ack_int = 1'b1;
end
end
default : begin
sramnext_state = S_IDLE;
end
endcase
end
always @(*)
begin
sram_wen_mem = 4'b0000;
if (ahbsram_size == 3'b010) begin
sram_wen_mem = {4{sram_wen}};
end
else if (ahbsram_size == 3'b001) begin
case (ahbsram_addr[1])
1'b0 : begin
sram_wen_mem[0] = sram_wen;
sram_wen_mem[1] = sram_wen;
sram_wen_mem[2] = 1'b0;
sram_wen_mem[3] = 1'b0;
end
1'b1 : begin
sram_wen_mem[0] = 1'b0;
sram_wen_mem[1] = 1'b0;
sram_wen_mem[2] = sram_wen;
sram_wen_mem[3] = sram_wen;
end
endcase
end
else if (ahbsram_size == 3'b000) begin
case (ahbsram_addr[1:0])
2'b00 : begin
sram_wen_mem[0] = sram_wen;
sram_wen_mem[1] = 1'b0;
sram_wen_mem[2] = 1'b0;
sram_wen_mem[3] = 1'b0;
end
2'b01 : begin
sram_wen_mem[0] = 1'b0;
sram_wen_mem[1] = sram_wen;
sram_wen_mem[2] = 1'b0;
sram_wen_mem[3] = 1'b0;
end
2'b10 : begin
sram_wen_mem[0] = 1'b0;
sram_wen_mem[1] = 1'b0;
sram_wen_mem[2] = sram_wen;
sram_wen_mem[3] = 1'b0;
end
2'b11 : begin
sram_wen_mem[0] = 1'b0;
sram_wen_mem[1] = 1'b0;
sram_wen_mem[2] = 1'b0;
sram_wen_mem[3] = sram_wen;
end
endcase
end
else begin
sram_wen_mem = {4{sram_wen}};
end
end
// SRAM Instantiations
sram_model #(
.SYNC_RESET (SYNC_RESET),
.ADDR_WIDTH (ADD_WIDTH)
)
u_sram_model (
.writedata (ahbsram_wdata),
.readdata (ram_rdata[31:0]),
.wren (sram_wen_mem),
.rden (sram_ren),
.writeaddr (ahbsram_addr[ADD_WIDTH-1:2]),
.readaddr (ahbsram_addr[ADD_WIDTH-1:2]),
.clk (HCLK),
.resetn (HRESETN)
);
always @(posedge HCLK or negedge aresetn)
begin
if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin
sramahb_rdata <= 32'h0;
end
else if (sram_ren_d == 1'b1) begin
sramahb_rdata <= ram_rdata;
end
else begin
sramahb_rdata <= sramahb_rdata;
end
end
always @(posedge HCLK or negedge aresetn)
begin
if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin
sram_ren_d <= 32'h0;
end
else begin
sram_ren_d <= sram_ren;
end
end
// Generate the SRAM done when the SRAM wren/rden is done
always @(posedge HCLK or negedge aresetn)
begin
if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin
sram_done <= 1'b0;
end
else if (sram_wen || sram_ren) begin
sram_done <= 1'b1;
end
else begin
sram_done <= 1'b0;
end
end
// Generate the SRAM ack
assign sramahb_ack = sramahb_ack_int;
endmodule
sram顶层
`timescale 1ns / 1ps
module ahb_sram #(
parameter SYNC_RESET = 1,
parameter AHB_DWIDTH = 32,
parameter AHB_AWIDTH = 32,
parameter SIZE_IN_BYTES = 2048,
parameter ADD_WIDTH = $clog2(SIZE_IN_BYTES)
)
(
//----------------------------------
// IO Declarations
//----------------------------------
// Inputs
input HCLK,
//时钟为所有总线传输提供时基。所有信号时序都和HCLK 的上升沿相关
input HRESETN,
//总线复位信号低有效并用来复位系统和总线。这是唯一的低有效的信号
input HSEL,
/*来源:译码器 每个 AHB 从机都有自己独立的从机选择信号并且用该
信号来表示当前传输是否是打算送给选中的从机。该
信号是地址总线的简单组合译码。*/
input HREADYIN,
/*当 HREADY 为高时表示总线上的传输已经完成。在扩
展传输时该信号可能会被拉低。 注意:总线上的从机要求 HREADY 作为输入输出信号。*/
input [1:0] HTRANS,
//表示当前传输的类型,可以是不连续、连续、空闲和忙。
input [2:0] HBURST,
//表示传输是否组成了突发的一部分。支持四个、八个
//或者 16 个节拍的突发传输并且突发传输可以是增量或者是回环。
input [2:0] HSIZE,
//表示传输的大小,典型情况是字节(8 位)、半字(16
//位)或者是字(32 位)。协议允许最大的传输大小可以达到 1024 位。
input [AHB_DWIDTH-1:0] HWDATA,
/*写数据总线用来在写操作期间从主机到总线从机传输
数据。建议最小的数据总线宽度为 32 位。然而,在要
求高带宽运行时扩展(数据总线)还是很容易的。*/
input [AHB_AWIDTH-1:0] HADDR,
/*32 位系统地址总线。*/
input HWRITE,
/*当该信号为高时表示一个写传输,为低的时候表示一个读传输。*/
// Outputs
output [AHB_DWIDTH-1:0] HRDATA,
/*读数据总线用来在读操作期间从总线从机向总线主机
传输数据。建议最小的数据总线宽度为 32 位。然而,
在要求高带宽运行时扩展(数据总线)还是很容易的。*/
output [1:0] HRESP,
/*传输响应给传输状态提供了附加信息。提供四种不同的响应:OKEY、ERROR、RETRY 和 SPLIT。*/
output HREADYOUT
/*当 HREADY 为高时表示总线上的传输已经完成。在扩
展传输时该信号可能会被拉低*/
);
//----------------------------------
// Variable Declarations
//----------------------------------
wire [ADD_WIDTH-1:0] HADDR_cal;
wire [2:0] ahbsram_size;
wire [ADD_WIDTH-1:0] ahbsram_addr;
wire [31:0] ahbsram_wdata;
wire ahbsram_write;
wire [31:0] sramahb_rdata;
wire sramahb_ack;
//----------------------------------
// Start of Main Code
//----------------------------------
assign HADDR_cal = HADDR[ADD_WIDTH-1:0];
// Instantiations
ahb_sram_if #(
.AHB_DWIDTH (AHB_DWIDTH),
.AHB_AWIDTH (AHB_AWIDTH),
.ADD_WIDTH (ADD_WIDTH),
.SYNC_RESET (SYNC_RESET)
)
u_ahb_sram_if (
.HCLK (HCLK),
.HRESETN (HRESETN),
.HSEL (HSEL),
.HTRANS (HTRANS),
.HBURST (HBURST),
.HWRITE (HWRITE),
.HWDATA (HWDATA),
.HSIZE (HSIZE),
.HADDR (HADDR_cal),
.HREADYIN (HREADYIN),
// From SRAM Control signals
.sramahb_ack (sramahb_ack),
.sramahb_rdata (sramahb_rdata),
// Outputs
.HREADYOUT (HREADYOUT),
.HRESP (HRESP),
// To SRAM Control signals
.ahbsram_req (ahbsram_req),
.ahbsram_write (ahbsram_write),
.ahbsram_wdata (ahbsram_wdata),
.ahbsram_size (ahbsram_size),
.ahbsram_addr (ahbsram_addr),
.HRDATA (HRDATA)
);
sram_ctrl_if #(
.ADD_WIDTH (ADD_WIDTH),
.SYNC_RESET (SYNC_RESET)
)
u_sram_ctrl_if (
.HCLK (HCLK),
.HRESETN (HRESETN),
// From AHB Interface signals
.ahbsram_req (ahbsram_req),
.ahbsram_write (ahbsram_write),
.ahbsram_wdata (ahbsram_wdata),
.ahbsram_size (ahbsram_size),
.ahbsram_addr (ahbsram_addr),
// Outputs
// To AHB Interface signals
.sramahb_ack (sramahb_ack),
.sramahb_rdata (sramahb_rdata)
);
endmodule
主机功能模型
module ahb_master #(
//----------------------------------
// Paramter Declarations
//----------------------------------
parameter START_ADDR = 0,
parameter DEPTH_IN_BYTES = 32'h100,
parameter END_ADDR = START_ADDR+DEPTH_IN_BYTES-1
)
(
//----------------------------------
// IO Declarations
//----------------------------------
input wire HRESETn,
input wire HCLK,
output reg [31:0] HADDR,
output reg [1:0] HTRANS,
output reg HWRITE,
output reg [2:0] HSIZE,
output reg [2:0] HBURST,
output reg [31:0] HWDATA,
input wire [31:0] HRDATA,
input wire [1:0] HRESP,
input wire HREADY
);
//----------------------------------
// Variable Declarations
//----------------------------------
reg [31:0] data_burst[0:1023];
//----------------------------------
// Start of Main Code
//----------------------------------
initial begin
HADDR = 0;
HTRANS = 0;
HWRITE = 0;
HSIZE = 0;
HBURST = 0;
HWDATA = 0;
while(HRESETn === 1'bx) @(posedge HCLK);
while(HRESETn === 1'b1) @(posedge HCLK);
while(HRESETn === 1'b0) @(posedge HCLK);
`ifdef SINGLE_TEST
repeat(3) @(posedge HCLK);
memory_test(START_ADDR, END_ADDR, 1);
memory_test(START_ADDR, END_ADDR, 2);
memory_test(START_ADDR, END_ADDR, 4);
`endif
`ifdef BURST_TEST
repeat(5) @(posedge HCLK);
memory_test_burst(START_ADDR, END_ADDR, 1);
memory_test_burst(START_ADDR, END_ADDR, 2);
memory_test_burst(START_ADDR, END_ADDR, 4);
memory_test_burst(START_ADDR, END_ADDR, 6);
memory_test_burst(START_ADDR, END_ADDR, 8);
memory_test_burst(START_ADDR, END_ADDR, 10);
memory_test_burst(START_ADDR, END_ADDR, 16);
memory_test_burst(START_ADDR, END_ADDR, 32);
memory_test_burst(START_ADDR, END_ADDR, 64);
memory_test_burst(START_ADDR, END_ADDR, 128);
memory_test_burst(START_ADDR, END_ADDR, 255);
repeat(5) @(posedge HCLK);
`endif
$finish(2);
end
//-----------------------------------------------------------------------
// Single transfer test
//-----------------------------------------------------------------------
task memory_test;
input [31:0] start; // start address
input [31:0] finish; // end address
input [2:0] size; // data size: 1, 2, 4
integer i;
integer error;
reg [31:0] data;
reg [31:0] gen;
reg [31:0] got;
reg [31:0] reposit[START_ADDR:END_ADDR];
begin
$display("%m: read-after-write test with %d-byte access", size);
error = 0;
gen = $random(7);
for (i = start; i < (finish-size+1); i = i + size) begin
gen = $random & ~32'b0;
data = align(i, gen, size);
ahb_write(i, size, data);
ahb_read(i, size, got);
got = align(i, got, size);
if (got !== data) begin
$display("[%10d] %m A:%x D:%x, but %x expected", $time, i, got, data);
error = error + 1;
end
end
if (error == 0)
$display("[%10d] %m OK: from %x to %x", $time, start, finish);
$display("%m read-all-after-write-all with %d-byte access", size);
error = 0;
gen = $random(1);
for (i = start; i < (finish-size+1); i = i + size) begin
gen = {$random} & ~32'b0;
data = align(i, gen, size);
reposit[i] = data;
ahb_write(i, size, data);
end
for (i = start; i < (finish-size+1); i = i + size) begin
data = reposit[i];
ahb_read(i, size, got);
got = align(i, got, size);
if (got !== data) begin
$display("[%10d] %m A:%x D:%x, but %x expected", $time, i, got, data);
error = error + 1;
end
end
if (error == 0)
$display("[%10d] %m OK: from %x to %x", $time, start, finish);
end
endtask
//-----------------------------------------------------------------------
// Burst transfer test
//-----------------------------------------------------------------------
task memory_test_burst;
input [31:0] start; // start address
input [31:0] finish; // end address
input [7:0] leng; // burst length
integer i;
integer j;
integer k;
integer r;
integer error;
reg [31:0] data;
reg [31:0] gen;
reg [31:0] got;
reg [31:0] reposit[0:1023];
integer seed;
begin
$display("%m: read-all-after-write-all burst test with %d-beat access", leng);
error = 0;
seed = 111;
gen = $random(seed);
k = 0;
if (finish > (start+leng*4)) begin
for (i = start; i < (finish-(leng*4)+1); i = i + leng*4) begin
for (j = 0; j < leng; j = j + 1) begin
data_burst[j] = $random;
reposit[j+k*leng] = data_burst[j];
end
@(posedge HCLK);
ahb_write_burst(i, leng);
k = k + 1;
end
gen = $random(seed);
k = 0;
for (i = start; i < (finish-(leng*4)+1); i = i + leng*4) begin
@(posedge HCLK);
ahb_read_burst(i, leng);
for (j = 0; j < leng; j = j + 1) begin
if (data_burst[j] != reposit[j+k*leng]) begin
error = error+1;
$display("%m A=%hh D=%hh, but %hh expected",i+j*leng, data_burst[j], reposit[j+k*leng]);
end
end
k = k + 1;
r = $random & 8'h0F;
repeat(r) @(posedge HCLK);
end
if (error == 0)
$display("%m %d-length burst read-after-write OK: from %hh to %hh",leng, start, finish);
end
else begin
$display("%m %d-length burst read-after-write from %hh to %hh ???",leng, start, finish);
end
end
endtask
//-----------------------------------------------------------------------
// As AMBA AHB bus uses non-justified data bus scheme, data should be
// aligned according to the address.
//-----------------------------------------------------------------------
function [31:0] align;
input [ 1:0] addr;
input [31:0] data;
input [ 2:0] size; // num of bytes
begin
`ifdef BIG_ENDIAN
case (size)
1 :
case (addr[1:0])
0 : align = data & 32'hFF00_0000;
1 : align = data & 32'h00FF_0000;
2 : align = data & 32'h0000_FF00;
3 : align = data & 32'h0000_00FF;
endcase
2 :
case (addr[1])
0 : align = data & 32'hFFFF_0000;
1 : align = data & 32'h0000_FFFF;
endcase
4 :
align = data&32'hFFFF_FFFF;
default :
$display($time,,"%m ERROR %d-byte not supported for size", size);
endcase
`else
case (size)
1 :
case (addr[1:0])
0 : align = data & 32'h0000_00FF;
1 : align = data & 32'h0000_FF00;
2 : align = data & 32'h00FF_0000;
3 : align = data & 32'hFF00_0000;
endcase
2 :
case (addr[1])
0 : align = data & 32'h0000_FFFF;
1 : align = data & 32'hFFFF_0000;
endcase
4 :
align = data&32'hFFFF_FFFF;
default :
$display($time,,"%m ERROR %d-byte not supported for size", size);
endcase
`endif
end
endfunction
`include "./ahb_transaction_tasks.v"
endmodule
`ifndef __AHB_TRANSACTION_TASKS_V__
`define __AHB_TRANSACTION_TASKS_V__
//-----------------------------------------------------------------------
// AHB Read Task
//-----------------------------------------------------------------------
task ahb_read;
input [31:0] address;
input [2:0] size;
output [31:0] data;
begin
@(posedge HCLK);
HADDR <= #1 address;
HTRANS <= #1 2'b10; // NONSEQ;
HBURST <= #1 3'b000; // SINGLE;
HWRITE <= #1 1'b0; // READ;
case (size)
1 : HSIZE <= #1 3'b000; // BYTE;
2 : HSIZE <= #1 3'b001; // HWORD;
4 : HSIZE <= #1 3'b010; // WORD;
default :
$display($time,, "ERROR: unsupported transfer size: %d-byte", size);
endcase
@(posedge HCLK);
while (HREADY !== 1'b1) @(posedge HCLK);
HTRANS <= #1 2'b0; // IDLE
@(posedge HCLK);
while (HREADY === 0) @(posedge HCLK);
data = HRDATA; // must be blocking
if (HRESP != 2'b00)
$display($time,, "ERROR: non OK response for read");
@(posedge HCLK);
end
endtask
//-----------------------------------------------------------------------
// AHB Write Task
//-----------------------------------------------------------------------
task ahb_write;
input [31:0] address;
input [2:0] size;
input [31:0] data;
begin
@(posedge HCLK);
HADDR <= #1 address;
HTRANS <= #1 2'b10; // NONSEQ
HBURST <= #1 3'b000; // SINGLE
HWRITE <= #1 1'b1; // WRITE
case (size)
1 : HSIZE <= #1 3'b000; // BYTE
2 : HSIZE <= #1 3'b001; // HWORD
4 : HSIZE <= #1 3'b010; // WORD
default :
$display($time,, "ERROR: unsupported transfer size: %d-byte", size);
endcase
@(posedge HCLK);
while (HREADY !== 1) @(posedge HCLK);
HWDATA <= #1 data;
HTRANS <= #1 2'b0; // IDLE
@(posedge HCLK);
while (HREADY === 0) @(posedge HCLK);
if (HRESP != 2'b00)
$display($time,, "ERROR: non OK response write");
@(posedge HCLK);
end
endtask
//-----------------------------------------------------------------------
// AHB Read Burst Task
//-----------------------------------------------------------------------
task ahb_read_burst;
input [31:0] addr;
input [31:0] leng;
integer i;
integer ln;
integer k;
begin
k = 0;
@(posedge HCLK);
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b10; // NONSEQ
if (leng >= 16) begin
HBURST <= #1 3'b111; // INCR16
ln = 16;
end
else if (leng >= 8) begin
HBURST <= #1 3'b101; // INCR8
ln = 8;
end
else if (leng >= 4) begin
HBURST <= #1 3'b011; // INCR4
ln = 4;
end
else begin
HBURST <= #1 3'b001; // INCR
ln = leng;
end
HWRITE <= #1 1'b0; // READ
HSIZE <= #1 3'b010; // WORD
@(posedge HCLK);
while (HREADY == 1'b0) @(posedge HCLK);
while (leng > 0) begin
for (i = 0; i < ln-1; i = i + 1) begin
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b11; // SEQ;
@(posedge HCLK);
while (HREADY == 1'b0) @(posedge HCLK);
data_burst[k%1024] <= HRDATA;
k = k + 1;
end
leng = leng - ln;
if (leng == 0) begin
HADDR <= #1 0;
HTRANS <= #1 0;
HBURST <= #1 0;
HWRITE <= #1 0;
HSIZE <= #1 0;
end
else begin
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b10; // NONSEQ
if (leng >= 16) begin
HBURST <= #1 3'b111; // INCR16
ln = 16;
end
else if (leng >= 8) begin
HBURST <= #1 3'b101; // INCR8
ln = 8;
end
else if (leng >= 4) begin
HBURST <= #1 3'b011; // INCR4
ln = 4;
end
else begin
HBURST <= #1 3'b001; // INCR1
ln = leng;
end
@(posedge HCLK);
while (HREADY == 0) @(posedge HCLK);
data_burst[k%1024] = HRDATA; // must be blocking
k = k + 1;
end
end
@(posedge HCLK);
while (HREADY == 0) @(posedge HCLK);
data_burst[k%1024] = HRDATA; // must be blocking
end
endtask
//-----------------------------------------------------------------------
// AHB Write Burst Task
// It takes suitable burst first and then incremental.
//-----------------------------------------------------------------------
task ahb_write_burst;
input [31:0] addr;
input [31:0] leng;
integer i;
integer j;
integer ln;
begin
j = 0;
ln = 0;
@(posedge HCLK);
while (leng > 0) begin
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b10; // NONSEQ
if (leng >= 16) begin
HBURST <= #1 3'b111; // INCR16
ln = 16;
end
else if (leng >= 8) begin
HBURST <= #1 3'b101; // INCR8
ln = 8;
end
else if (leng >= 4) begin
HBURST <= #1 3'b011; // INCR4
ln = 4;
end
else begin
HBURST <= #1 3'b001; // INCR
ln = leng;
end
HWRITE <= #1 1'b1; // WRITE
HSIZE <= #1 3'b010; // WORD
for (i = 0; i < ln-1; i = i + 1) begin
@(posedge HCLK);
while (HREADY == 1'b0) @(posedge HCLK);
HWDATA <= #1 data_burst[(j+i)%1024];
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b11; // SEQ;
while (HREADY == 1'b0) @(posedge HCLK);
end
@(posedge HCLK);
while (HREADY == 0) @(posedge HCLK);
HWDATA <= #1 data_burst[(j+i)%1024];
if (ln == leng) begin
HADDR <= #1 0;
HTRANS <= #1 0;
HBURST <= #1 0;
HWRITE <= #1 0;
HSIZE <= #1 0;
end
leng = leng - ln;
j = j + ln;
end
@(posedge HCLK);
while (HREADY == 0) @(posedge HCLK);
if (HRESP != 2'b00) begin // OKAY
$display($time,, "ERROR: non OK response write");
end
`ifdef DEBUG
$display($time,, "INFO: write(%x, %d, %x)", addr, size, data);
`endif
HWDATA <= #1 0;
@(posedge HCLK);
end
endtask
`endif
`ifndef __AHB_TRANSACTION_TASKS_V__
`define __AHB_TRANSACTION_TASKS_V__
//-----------------------------------------------------------------------
// AHB Read Task
//-----------------------------------------------------------------------
task ahb_read;
input [31:0] address;
input [2:0] size;
output [31:0] data;
begin
@(posedge HCLK);
HADDR <= #1 address;
HTRANS <= #1 2'b10; // NONSEQ;
HBURST <= #1 3'b000; // SINGLE;
HWRITE <= #1 1'b0; // READ;
case (size)
1 : HSIZE <= #1 3'b000; // BYTE;
2 : HSIZE <= #1 3'b001; // HWORD;
4 : HSIZE <= #1 3'b010; // WORD;
default :
$display($time,, "ERROR: unsupported transfer size: %d-byte", size);
endcase
@(posedge HCLK);
while (HREADY !== 1'b1) @(posedge HCLK);
HTRANS <= #1 2'b0; // IDLE
@(posedge HCLK);
while (HREADY === 0) @(posedge HCLK);
data = HRDATA; // must be blocking
if (HRESP != 2'b00)
$display($time,, "ERROR: non OK response for read");
@(posedge HCLK);
end
endtask
//-----------------------------------------------------------------------
// AHB Write Task
//-----------------------------------------------------------------------
task ahb_write;
input [31:0] address;
input [2:0] size;
input [31:0] data;
begin
@(posedge HCLK);
HADDR <= #1 address;
HTRANS <= #1 2'b10; // NONSEQ
HBURST <= #1 3'b000; // SINGLE
HWRITE <= #1 1'b1; // WRITE
case (size)
1 : HSIZE <= #1 3'b000; // BYTE
2 : HSIZE <= #1 3'b001; // HWORD
4 : HSIZE <= #1 3'b010; // WORD
default :
$display($time,, "ERROR: unsupported transfer size: %d-byte", size);
endcase
@(posedge HCLK);
while (HREADY !== 1) @(posedge HCLK);
HWDATA <= #1 data;
HTRANS <= #1 2'b0; // IDLE
@(posedge HCLK);
while (HREADY === 0) @(posedge HCLK);
if (HRESP != 2'b00)
$display($time,, "ERROR: non OK response write");
@(posedge HCLK);
end
endtask
//-----------------------------------------------------------------------
// AHB Read Burst Task
//-----------------------------------------------------------------------
task ahb_read_burst;
input [31:0] addr;
input [31:0] leng;
integer i;
integer ln;
integer k;
begin
k = 0;
@(posedge HCLK);
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b10; // NONSEQ
if (leng >= 16) begin
HBURST <= #1 3'b111; // INCR16
ln = 16;
end
else if (leng >= 8) begin
HBURST <= #1 3'b101; // INCR8
ln = 8;
end
else if (leng >= 4) begin
HBURST <= #1 3'b011; // INCR4
ln = 4;
end
else begin
HBURST <= #1 3'b001; // INCR
ln = leng;
end
HWRITE <= #1 1'b0; // READ
HSIZE <= #1 3'b010; // WORD
@(posedge HCLK);
while (HREADY == 1'b0) @(posedge HCLK);
while (leng > 0) begin
for (i = 0; i < ln-1; i = i + 1) begin
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b11; // SEQ;
@(posedge HCLK);
while (HREADY == 1'b0) @(posedge HCLK);
data_burst[k%1024] <= HRDATA;
k = k + 1;
end
leng = leng - ln;
if (leng == 0) begin
HADDR <= #1 0;
HTRANS <= #1 0;
HBURST <= #1 0;
HWRITE <= #1 0;
HSIZE <= #1 0;
end
else begin
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b10; // NONSEQ
if (leng >= 16) begin
HBURST <= #1 3'b111; // INCR16
ln = 16;
end
else if (leng >= 8) begin
HBURST <= #1 3'b101; // INCR8
ln = 8;
end
else if (leng >= 4) begin
HBURST <= #1 3'b011; // INCR4
ln = 4;
end
else begin
HBURST <= #1 3'b001; // INCR1
ln = leng;
end
@(posedge HCLK);
while (HREADY == 0) @(posedge HCLK);
data_burst[k%1024] = HRDATA; // must be blocking
k = k + 1;
end
end
@(posedge HCLK);
while (HREADY == 0) @(posedge HCLK);
data_burst[k%1024] = HRDATA; // must be blocking
end
endtask
//-----------------------------------------------------------------------
// AHB Write Burst Task
// It takes suitable burst first and then incremental.
//-----------------------------------------------------------------------
task ahb_write_burst;
input [31:0] addr;
input [31:0] leng;
integer i;
integer j;
integer ln;
begin
j = 0;
ln = 0;
@(posedge HCLK);
while (leng > 0) begin
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b10; // NONSEQ
if (leng >= 16) begin
HBURST <= #1 3'b111; // INCR16
ln = 16;
end
else if (leng >= 8) begin
HBURST <= #1 3'b101; // INCR8
ln = 8;
end
else if (leng >= 4) begin
HBURST <= #1 3'b011; // INCR4
ln = 4;
end
else begin
HBURST <= #1 3'b001; // INCR
ln = leng;
end
HWRITE <= #1 1'b1; // WRITE
HSIZE <= #1 3'b010; // WORD
for (i = 0; i < ln-1; i = i + 1) begin
@(posedge HCLK);
while (HREADY == 1'b0) @(posedge HCLK);
HWDATA <= #1 data_burst[(j+i)%1024];
HADDR <= #1 addr;
addr = addr + 4;
HTRANS <= #1 2'b11; // SEQ;
while (HREADY == 1'b0) @(posedge HCLK);
end
@(posedge HCLK);
while (HREADY == 0) @(posedge HCLK);
HWDATA <= #1 data_burst[(j+i)%1024];
if (ln == leng) begin
HADDR <= #1 0;
HTRANS <= #1 0;
HBURST <= #1 0;
HWRITE <= #1 0;
HSIZE <= #1 0;
end
leng = leng - ln;
j = j + ln;
end
@(posedge HCLK);
while (HREADY == 0) @(posedge HCLK);
if (HRESP != 2'b00) begin // OKAY
$display($time,, "ERROR: non OK response write");
end
`ifdef DEBUG
$display($time,, "INFO: write(%x, %d, %x)", addr, size, data);
`endif
HWDATA <= #1 0;
@(posedge HCLK);
end
endtask
`endif
`timescale 1ns / 1ps
module sram_model #(
parameter SYNC_RESET = 0,
parameter ADDR_WIDTH = 16
)
(
//----------------------------------
// IO Declarations
//----------------------------------
input [31:0] writedata,
output [31:0] readdata,
input [3:0] wren,
input rden,
input [ADDR_WIDTH-1:2] writeaddr,
input [ADDR_WIDTH-1:2] readaddr,
input clk,
input resetn
);
//----------------------------------
//--Local Parameter Declarations
//----------------------------------
localparam AWT = ((1<<(ADDR_WIDTH-2))-1);
//----------------------------------
// Variable Declarations
//----------------------------------
reg [7:0] bram0[AWT:0];
reg [7:0] bram1[AWT:0];
reg [7:0] bram2[AWT:0];
reg [7:0] bram3[AWT:0];
reg [ADDR_WIDTH-3:0] addr_q1;
reg rden_r = 1'b0;
wire [31:0] readdata_i;
wire aresetn;
wire sresetn;
//----------------------------------
// Start of Main Code
//----------------------------------
assign aresetn = (SYNC_RESET == 1) ? 1'b1 : resetn;
assign sresetn = (SYNC_RESET == 1) ? resetn : 1'b1;
always @(posedge clk)
begin
rden_r <= rden;
end
// Infer Block RAM
always @(posedge clk)
begin
if (wren[0])
bram0[writeaddr] <= writedata[7:0];
if (wren[1])
bram1[writeaddr] <= writedata[15:8];
if (wren[2])
bram2[writeaddr] <= writedata[23:16];
if (wren[3])
bram3[writeaddr] <= writedata[31:24];
end
always @(posedge clk)
begin
addr_q1 <= readaddr[ADDR_WIDTH-1:2];
end
assign readdata_i = {bram3[addr_q1],bram2[addr_q1],bram1[addr_q1],bram0[addr_q1]};
assign readdata = rden_r ? readdata_i : {32{1'b0}};
endmodule
schematic:
突发传输
代码里添一句宏定义即可开始仿真
`define SINGLE_TEST
console输出:
功能完全正确。
波形图:
HREADYIN 高阻,查看代码和schematic,我认为是因为在top_tb中他直接被拉高所以被优化掉了,造成了HREADYIN空驱动的情形;
试着给HREADYIN加了一句(* DONT_TOUCH= “TRUE” *) ,还是一样的。那可能不是HREADYIN而是HREADYIN的驱动被优化了,或者直接接到HREADYIN下一级去了。
不过看代码原作者“托管小弟”截取的图HREADYIN是被成功拉高的。
难道vcs综合的和vivado不一样?
往虚拟机上下了套edatools,自己用vcs跑了一下,还是高阻。。。
总的schematic看着没啥问题,但点这个信号的schematic或者showpath,直接没反应,看来确实是被优化了。
可能原作者用了些禁止优化、限制优化的源语或者vcs命令吧。想来这就是个testbench,写的master也是功能模型,这个仿真信号再深究也没有意义。
单个传输:
总结与疑问
vcs&verdi或者vcs加dve比使用vivado进行前仿好用多了
- 编译、仿真的速度快得多
- 可以直接在vcs命令中进行宏定义+define+SINGLE_TEST 改为+define+DUMP_VPD就可以改变宏定义,而不需要去修改代码
- 使用脚本运行比在vivado上使用鼠标点效率更高
- 对文件的管理更直观,自由度更高
不能使用DUMP函数的问题
- 最开始报错Undefined System Task call to $fsdbDumpfile ,于是加上了p操作。
- 由于一直找不到pli文件,采用-debug_pp -fsdb_old自动调用pli文件
- 提示没有配置novasHOME,到bashrc里配置好了并source一下重置
- 还是无法打开,多方查找办法,可能是版本问题
四、ASB总线
asb总线由于其总线带宽最高只支持到32bits、三态双向特性不适合DFT等缺点,已经几乎被淘汰了,现在很少有asb的设计。所以这里我也没有进行学习、实现。
五、APB桥
简单来说,apb桥就是将ahb上的高速转化为apb上的低速信号,再传递给apb上对应的外设。apb桥是apb外设的唯一主机。
桥接单元将系统总线传输转换成 APB 传输并实现下列功能:
z 锁存地址并使之在整个传输期间有效;
z 译码地址和产生一个外设选择信号,PSELx。在一个传输期间只有一个选择信号有
效;
z 对于写传输驱动数据到 APB 上;
z 对于读传输驱动 APB 数据到系统总线上;
z 为传输产生一个时序选通信号,PENABLE。
apb桥的传输示意图:
参考链接
- eetop.cn_AMBA总线规范_cn_20.pdf
- moshanghongfeng翻译的AMBA总线规范。链接: https://blog.csdn.net/moshanghongfeng/article/details/108931201
- “托管小弟”的专栏“AMBA协议详解与Verilog实现”
- “e网课”《数字ic入门》17到19讲,amba总线介绍。链接:https://www.bilibili.com/video/BV1K34y1B7hR/?spm_id_from=333.788&vd_source=2848d4df68e7c5d5183ab1c8dedb5f96