基于AHB_BUS Clac slave详解
基于AHB-APB BUS slave详解
1.目录
- 高内聚:让模块的功能更集中,更单一。
- AMBA总线例子,需要有一个模块和AMBA进行交互,就可以单独将与AHB总线进行交互的部分作为一个模块。经常需要一个模块处理ahb信号,可以设置ahb_slave_if.v模块
- 轻耦合,两个模块之间的交互信号比较简单
2.模块描述
*** 寄存器配置种类:第一类,config(Master配置到slave),操作数和操作模式,比如图像处理器中的图像规格;第二类,启动信号;第三类,状态寄存器,当IP完成任务或者出现错误的时候,更新模块的状态。**
3.设计划分
- Top模块不写逻辑,只进行模块的集成,避免gluelogic产生
3.1 项目目录规划
- env
- regression
- rtl
- sim
- sim_ncverilog
- sim_vcs
- tb
- tc
3.1.1 Env
- Env目录下放置的是一些Interface的接口
- Interface定义了AHB的一些接口,AHB总线的一些接口是固定的,不同的项目使用相同版本的AHB使用的总线接口是一致的。后续使用的时候可以类似于例化module一样,进行例化AHB
- Interface是sv中的概念
// interface ahb_if(input logic,input hrestn);
interface ahb_if();
logic hclk;
logic hrestn;
logic hsel;
logic hwrite;
logic hready;//hready_in
logic [2:0] hsize;
logic [2:0] hburst;
logic [1:0] htrans;
logic [31:0] hwdata;
logic [31:0] haddr;
logic hready_resp;
logic [1:0] hresp;
logic [31:0] hrdata;
reg [31:0] rdata;
// clock generator
initial
begin
hclk = 0;
forver
begin
#10 hclk = ~hclk;
end
end
parameter IDLE = 2'b00,
BUSY = 2'b01,
NONSEQ = 2'b10,
SEQ = 2'b11;
initial
begin
hrestn = 0;
htrans = IDLE;
hsize = 3'b00;
hburst = 3'b00;
hwrite = 0;
hsel = 0;
hready = 0;
haddr = 0;
#200;
hrestn = 1;
end
task ahb_write(input [31:0] addr,input [31:0] wdata);
begin
@(posedge hclk);
#1
hsize = 3'b10;
htrans = NONSEQ;
hwrite = 1;
hsel = 1;
hready = 1;
haddr = addr;
@(posedge hclk)
#1
hsize = 2'b00;
htrans = IDLE;
hwrite = 0;
hsel = 0;
hwdata = wdata;
@(posedge hclk);
#1
haddr = 32'b0;
@(posedge hclk);
end
endtask
task ahb_read(input [31:0] addr,output [31:0] rdata);
begin
@(posedge hclk);
#1
haddr = addr;
hsize = 2'b10;
htrans = NONSEQ;
hwrite = 0;
hsel = 1;
@(posedge hclk);
#1
hsize = 2'b00;
htrans = IDLE;
hwrite = 0;
hsel = 0;
@(posedge hclk)
rdata = hrdata;
haddr = 32'b0;
@(posedge hclk);
end
endtask
endinterface
3.1.2 regression
- regression中放置一个脚本,将许多testcase同过脚本进行管理
- 在项目中后期的时候,每修改一次代码,都要跑一下regression
3.1.3 RTL
- 顶层模块,ahb_slave_clac.v
hready
*以AHB lite为例,只有一个Master,所以不需要Arbiter,有多个slave
- 假设addr1对应slave1,addr2对应slave2。假设T0上升沿将地址addr1驱动到总线上,T1时候,slave1的hready_out是拉低的,要进行延迟。在T1上升沿的时候会将下一个地址addr2驱动到总线上。T2上升沿的时候将slave1的hready_out拉高,此时可以将addr1的数据驱动到总线上,但是在T2上升沿时不能驱动addr2的数据的,因为addr1的响应还没有结束。需要在T3上升沿的时候驱动addr2的数据。
- slave2如何知道salve1的响应时完成的呢,可以将所有的slave的输出hready_out进行与操作(进行与操作这种方式是默认在slave没有被选中的时候,hready_resp默认是拉高的)或者mux,将与之后的信号广播到所有的slave,这个信号就是hready_in
- 假设T1的时候slave1的hready_out = 0,0与任何信号输出0,hready_in就是0,hready作为slave2的输入,slave2看到hready_in为0的时候,在下一个周期上升沿就不能驱动数据到总线上。只有当slave2输入的hready_in为1的时候,在下一个周期上升沿才能驱动数据到总线上。
- hready_in的作用就是告诉slave当前总线可以驱动数据
// module ahb_clac_top
module ahb_clac_top (
hclk,
hrestn,
hsel,
hwrite,
hsize,
htrans,
hburst,
hwdata,
haddr,
hready_resp,
hresp,
hrdata
);
input hclk;
input hrestn;
input hsel;
input hwrite;
input hready;
input [2:0] hsize;
input [2:0] htrans;
input [2:0] hburst;
input [1:0] htrans;
input [31:0]hwdata;
input [7:0] haddr;
output hready_resp;
output [1:0] hresp;
output [31:0] hrdata;
wire ctrl;
wire [1:0] clac_mode;
wire [31:0] opcode_a;
wire [31:0] opcode_b;
wire [31:0] result;
ahb_slave_clac U_slave_if (
.hclk (hclk),
.hsel (hrestn),
.hwrite (hwrite),
.hsize (hsize),
.hready (hready),
.htrans (htrans),
.hburst (hburst),
.hwdata (hwdata),
.haddr (haddr),
.result (result),
.hready_resp (hready_resp),
.hresp (hresp),
.hrdata (hrdata),
.ctrl (ctrl),
.clac_mode (clac_mode),
.opcode_a (opcode_a),
.opcode_b (opcode_b)
);
clac U_clac (
.ctrl (ctrl),
.clac_mode (clac_mode),
.opcode_a (opcode_a),
.opcode_b (opcode_b),
.result (result)
);
endmodule
- 计算模块 clac.v
- 输入操作数,输入clac_mode(操作符),使能信号;输出result
- 使能信号下,根据clac_mode进行操作,模块不使能的条件下,恒定输出为0
- 在书写case语句的时候,需要使用default
- cass语句能够覆盖所有的情况可以不用写default,最好写default
注意位宽不匹配的问题
assign a[3:0] = b[7:0] # 会进行截断
assign a[7:0] = b[3:0] # 综合的时候会报风险
//clac
module clac(
ctrl, // 使能信号
clac_mode,
opcode_a,
opcode_b,
result
);
input ctrl;
input [31:0] opcode_a;
input [31:0] opcode_b;
input [1:0] clac_mode;
output [31:0] result;
reg [31:0] result;
always @(*)
begin
if(ctrl)
case(clac_mode)
2'b00:result = opcode_a & opcode_b;
2'b01:result = opcode_a | opcode_b;
2'b10:result = opcode_a ^ opcode_b;
2'b11:result = opcode_a + opcode_b;
default:result = 32'b0;
endcase
else
result = 32'b0;
end
endmodule
- ahb_slave_clac.v
- 这个模块主要进行接口信号处理,将接口信号的输出,比如运算符和使能信号给计算模块,计算模块返回结果,ahb_slave将计算模块返回的结果作为hrdata返回给ahb总线
ahb_slave接口模块的设计思路 - ahb的传输有两个阶段,一个地址阶段,一个数据阶段。
- AHB\APB\AXI地址都是以byte为单位的。
- CPU通过总线配置模块中的寄存器,每个寄存器都有自己的地址空间。CPU读到写代码之后,AHB会发起写请求,根据地址找到slave,拉高hsel信号,将数据写到对应地址的寄存器中
- 在进行设计的时候,需要将两个阶段进行对齐,如果不进行对齐,可能看到地址的时候,数据还没有准备好或者拿到数据的时候,地址已经进行了切换。解决方式就是将控制信号进行打拍的处理。
- 为什么地址信号需要打一拍,因为AHB是分为两个阶段进行执行的,也就是说数据阶段要滞后地址阶段一个周期,所以将地址haddr打一拍,延迟一个周期,这样地址数据可以和写数据一起到组合逻辑中
- 打一拍之后的信号是haddr_r,就可以和写数据共同将数据写到寄存器中。
- 在组合逻辑中会产生什么时候写操作数(opcode_a,opcode_b),什么时候写clac mode,根据地址,判断地址落到哪一个寄存器上,可以将数据写道该寄存器中。
- 对于读数据,T0时刻时钟上升沿,将haddr驱动到总线上,haddr打一拍之后,T1周期上升沿haddr_r驱动到总线上(相当于将haddr打一拍,沿长一个周期)。在T1周期内有地址和操作信号,经过组合逻辑产生hrdata。
代码
1、先进行打拍处理,将address phaze和data phaze进行对齐
注意:hesel拉高和hready(拉高)表示当前的输入是有效的,hsel拉高表示选择当前的slave,hready拉高表示当前总线可以驱动数据,将ahb信号进行打拍
2、生成读或者是写的操作信号,
3、hready_resp表示能不能再下一个周期能够拉起hready_out
4、写操作,在写信号下,根据地址,判断将写数据给哪个寄存器
5、读操作,在读信号下,根据地址,读取数据
//ahb_slave_clac slave接口模块
module ahb_slave_clac(
// input signals
hclk,
hrestn, //时钟复位信号
hsel,
htrans,
hwrite,
hsize,
hburst,
haddr, //控制信号
hwdata,
hready, // hready_in,所有slave输出的hready_out,经过与门之后广播到所有slave
result,
// output signals
hready_resp,
hrdata,
hresp,
ctrl,
opcode_a, // 配置寄存器信号
opcode_b,
clac_mode
);
input hclk;
input hrestn;
input hsel;
input hwrite;
input hready;
input [2:0] hsize;
input [2:0] htrans;
input [2:0] hburst;
input [31:0]hwdata;
input [7:0] haddr;
input [31:0] result; // 要将clac返回的结果作为rdata给总线
output hready_resp;
output [1:0] hresp;
output [31:0] hrdata; // 输出给AHB总线的
output ctrl;
output [1:0] clac_mode;
output [31:0] opcode_a;
output [31:0] opcode_b; // 输出给计算模块的
reg hwrite_r;
reg [2:0] hsize_r;
reg [2:0] hburst_r;
reg [1:0] htrans_r; // addtress phaze阶段的信号打一拍,配置地址和控制信号寄存器
reg [7:0] haddr_r;
reg [31:0] hrdata; // 配置读数据寄存器
wire ahb_write;
wire ahb_read;
reg enable_r; // 配置clac寄存器,需要CPU进行配置,通过AHB总线传递数据
reg [1:0] ctrl_r;
reg [31:0] opa_r;
reg [31:0] opb_r;
// 设置htrans状态参数
parameter IDLE = 2'b00,
BUSY = 2'b01,
NONSEQ = 2'b10,
SEQ = 2'b11;
// 设置配置寄存器的地址
parameter ENABLE_ADDR = 8'h00,
CTRL_ADDR = 8'h4,
OPA_ADDR = 8'h08,
OPB_ADDR = 8'h0c,
RESULT_ADDR = 8'h10;
// 将address phaze阶段的信号打一拍
always@(posedge hclk or negedge hrestn);
begin
if(!hrestn)
begin
hwrite_r <= 1'b0;
hsize_r <= 2'b0;
hburst_r <= 3'b0;
htrans_r <= 2'b0;
haddr_r <= 8'b0;
end
else if(hready && hsel)
begin
hwrite_r <= hwrite;
hsize_r <= hsize;
hburst_r <= hburst;
htrans_r <= htrans; // 将address phaze数据与data phaze对齐
haddr_r <= haddr;
end
end
assign ctrl = enable_r;
assign clac_mode = ctrl_r;
assign opcode_a = opa_r;
assign opcode_b = opb_r;
assign hready_resp = 1'b1; // hready_resp 固定为1表示能够随时输出计算结果
assign hresp = 2'b00;
// 产生读写数据的控制信号
// 根据AHB传递过来的信号进行读写信号的判断
assign ahb_write = ((htrans_r == NONSEQ)|| (htrans_r == SEQ)) && hwrite_r && hready;
assign ahb_read = ((htrans_r == NONSEQ)|| (htrans_r == SEQ)) && (!hwrite_r) && hready;
// 进行写操作,写操作数和操作符和使能寄存器
always @(posedge hclk or negedge hrestn)
begin
if(!hrestn)
begin
enable_r <= 1'b0;;
ctrl_r <= 2'b0;
opa_r <= 16'b0;
opb_r <= 16'b0;
end
else if(ahb_write)
begin
case(haddr_r)
ENABLE_ADDR : enable_r = hwdata[0];
CTRL_ADDR : ctrl_r = hwdata[1:0];
OPA_ADDR : opa_r = hwdata[15:0];
OPB_ADDR : opb_r = hwdata[15:0];
endcase
end
end
//进行读操作,读出clac返回的结果
always @(*)
begin
if(ahb_read)
begin
case(haddr_r[7:0])
ENABLE_ADDR :hrdata = {31'b0,enable_r};
CTRL_ADDR :hrdata = {31'b0,ctrl_r};
OPA_ADDR :hrdata = {16'b0,opa_r};
OPB_ADDR :hrdata = {16'b0,opb_r};
RESULT_ADDR :hrdata = result;
default :hrdata = 32'h0;
endcase
end
else
hrdata = 32'h0;
end
endmodule
3.1.4 sim_vcs
存放的是vcs仿真的Makefile文件
3.1. tb
4.总结
1.两阶段对齐
2.产生读写信号
3.配置寄存器(根据寄存器地址写寄存器,在模块当中定义寄存器和寄存器地址)
4.写输出
5.读数据