基于AHB-APB BUS slave详解

1.目录

  • 高内聚:让模块的功能更集中,更单一。AMBA总线例子,需要有一个模块和AMBA进行交互,就可以单独将其作为一个模块。
  • 轻耦合,两个模块之间的交互信号比较简单

2.模块描述

*** 寄存器配置种类:第一类,config(Master配置到slave),比如图像处理器中的图像规格;第二类,启动信号;第三类,状态寄存器,当IP完成任务或者出现错误的时候,更新模块的状态。**

3.设计划分

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
基于AHB-APB BUS 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进行与操作或者mux,将与之后的信号广播到所有的slave,这个信号就是hready_in
  • 假设T1的时候slave1的hready_out = 0,0与任何信号输出0,hready_in就是0,hready作为slave2的输入,slave2看到hready_in为0的时候,在下一个周期上升沿就不能驱动数据到总线上。只有当slave2输入的hready_in为1的时候,在下一个周期上升沿才能驱动数据到总线上。
// 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语句的时候,需要使用
//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打一拍,延迟一个周期,这样地址数据可以和写数据一起到组合逻辑中
    基于AHB-APB BUS slave详解-小白菜博客
  • 打一拍之后的信号是haddr_r,就可以和写数据共同将数据写到寄存器中。
  • 在组合逻辑中会产生什么时候写操作数(opcode_a,opcode_b),什么时候写clac mode,根据地址,判断地址落到哪一个寄存器上,可以将数据写道该寄存器中。
  • 对于读数据,T0时刻时钟上升沿,将haddr驱动到总线上,haddr打一拍之后,T1周期上升沿haddr_r驱动到总线上(相当于将haddr打一拍,沿长一个周期)。在T1周期内有地址和操作信号,经过组合逻辑产生hrdata。

代码

1、先进行打拍处理
注意:hesel拉高和hready(拉高)表示当前的输入是有效的,将输入进行
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
	
	// output signals
	result,
	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 [1:0] htrans;
	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;
	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寄存器
	reg [1:0] ctrl_r; 
	reg [31:0] opa_r;
	reg [31:0] opb_r;
	
	
	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
	
	//进行读操作
	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文件