【芯片架构】寄存器模型基本原理

系列文章目录

寄存器表单,寄存器设计代码,寄存器ral文件

概述

在SOC系统中,每个子系统或模块往往包含一组控制端口,通过控制端口可以配置模块中的寄存器,模块功能可以依据寄存器的值改变,这组控制端口就是寄存器配置总线,常见的寄存器配置总线有APB/AHB/OCP总线等等。一个简单的模块代码如下:

 module dut(
	input clk,
	input rst_n,
	input bus_cmd_valid,
	input bus_op,
	input [31:0] bus_addr,
	input [31:0] bus_wr_data,
	output reg [31:0] bus_rd_data,
	input [7:0] rxd,
	input rx_dv,
	output reg [7:0] txd,
	output reg tx_en
);

	reg invert;

	always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			txd <= 8'b0;
			tx_en <= 1'b0;
		end
		else if(invert) begin
			txd <= ~rxd;
			tx_en <= rx_dv;
		end
		else begin
			txd <= rxd;
			tx_en <= rx_dv;
		end
	end

	always @(posedge clk or negedge rst_n) begin
		if(!rst_n)
			invert <= 1'b0;
		else if(bus_cmd_valid && bus_op) begin
			case(bus_addr)
				32'h8: invert <= bus_wr_data[0];
				default: ;
			endcase
		end
	end

	always @(posedge clk or negedge rst_n) begin
		if(!rst_n)
			bus_rd_data <= 32'b0;
		else if(bus_cmd_valid && !bus_op) begin
			case(bus_addr)
				32'h8: bus_rd_data <= {31'b0, invert};
				default: bus_rd_data <= 32'b0;
			endcase
		end
	end

endmodule

上述代码中,第4-8行为寄存器配置总线。
第32-44行,通过寄存器配置总线写寄存器的值,模块中只有一个变量叫invert。
invert变量会控制模块的功能,当invert为零时,输出txd与输入rxd的值相等;当invert为一时,输出txd与输入rxd的值相反。

除了invert变量这种可以改变模块功能,还可以表示模块内部的一些状态,对上述代码做一些小小的改变:

 module dut(
	input clk,
//第1-32行代码保持不变
	always @(posedge clk or negedge rst_n) begin
		if(!rst_n)
			tx_en_cnt <= 32'd0;
		else if(rx_dv)
			tx_en_cnt <= tx_en_cnt + 32'd1;			
	end
	
	always @(posedge clk or negedge rst_n) begin
		if(!rst_n)
			invert <= 1'b0;
		else if(bus_cmd_valid && bus_op) begin
			case(bus_addr)
				32'h8: invert <= bus_wr_data[0];
				default: ;
			endcase
		end
	end

	always @(posedge clk or negedge rst_n) begin
		if(!rst_n)
			bus_rd_data <= 32'b0;
		else if(bus_cmd_valid && !bus_op) begin
			case(bus_addr)
				32'h8: 	bus_rd_data <= {31'b0, invert};
				32'hC: 	bus_rd_data <= tx_en_cnt;
				default: 
					bus_rd_data <= 32'b0;
			endcase
		end
	end
);

上述代码中新增tx_en_cnt,表示模块已经发送了多少次txd数据。配置总线只能读不能写。

上述代码中配置总线位宽为32bit,invert只用了1bit,如果新增加一个sum表示txd的输出是输入数据bit位的总和,且sum和invert在同一个配置总线地址上,对上述代码再做一些小小的改变:

 module dut(
	input clk,
//第1-32行代码保持不变
	reg invert;
	reg sum;

	always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			txd <= 8'b0;
			tx_en <= 1'b0;
		end
		else if(invert) begin
			txd <= ~rxd;
			tx_en <= rx_dv;
		end
		else if(sum) begin
			txd <= rxd[0] + rxd[1] + rxd[2] + rxd[3];
			tx_en <= rx_dv;
		end
		else begin
			txd <= rxd;
			tx_en <= rx_dv;
		end
	end
	
	always @(posedge clk or negedge rst_n) begin
		if(!rst_n)
			invert <= 1'b0;
		else if(bus_cmd_valid && bus_op) begin
			case(bus_addr)
				32'h8: begin
					invert <= bus_wr_data[0];
					sum <= bus_wr_data[1];
				end
				default: ;
			endcase
		end
	end

	always @(posedge clk or negedge rst_n) begin
		if(!rst_n)
			bus_rd_data <= 32'b0;
		else if(bus_cmd_valid && !bus_op) begin
			case(bus_addr)
				32'h8: bus_rd_data <= {30'b0,sum,invert};
				default: bus_rd_data <= 32'b0;
			endcase
		end
	end

上述代码中称sum和invert为寄存器域,而32’h8称为一个寄存器,由于配置总线数据位宽为32bit,占据四个地址,所以从地址32’h8-32’hC称为一个寄存器。
每个寄存器可以由多bit组成,每个域也可以由多bit组成。域是寄存器的最小粒度,每个域代表不同的含义和功能。比如:
在这里插入图片描述
uart_data就是一个寄存器,parity_error就是一个域,data也是一个域。parity_error域代表UART模块发生了校验位检测错误,CPU读取uart_data寄存器,一看parity_error域值为1就知道UART模块发生了校验位检测错误。
data域代表待发送的UART帧数据,CPU通过写uart_data寄存器就能将想发送的数据发送给UART模块,由UART模块组UART帧进行发送。

寄存器设计代码

为了更好的理解寄存器的功能,我们用一个实例来说明:
在这里插入图片描述

在UART模块中,使用D触发器实现parity_mode寄存器域段,用2bit表示,verilog代码如下:

always@(posedge clk or negedge rst_n) begin
	if(~rst_n)
		parity_mode <= 2'd0;
	else if(psel & penable & pwrite & paddr[15:0] == 16'h0008)
		parity_mode <= pwdata[2:1];
end

第四行代码含义是CPU可以SOC总线(APB总线)来访问寄存器,当APB总线psel且penable有效且是写操作,且地址低16bit为0x0008时,写数据的bit2-bit1写入到parity_mode中。暂且先不用关心地址为什么是0x0008。
UART模块可以依据CPU配置的parity_mode值决定校验位生成模式,从而实现不同的逻辑功能。

中心化寄存器管理

在项目中,寄存器数据量有时候会很庞大,为了统一方便管理,通常首先用excel表单来记录寄存器相关信息,如下:
在这里插入图片描述
确认excel寄存器表单无误后,通过脚本生成Verilog设计代码及验证所需的寄存器ral模型。

寄存器表单

寄存器表单地址

寄存器表单中与地址相关的概念有:base_addr,addr_width,data_width,offset。
首先先了解一下SOC系统中有关CPU地址空间的概念:
CPU在SOC总线中是通过不同的地址空间去进行访问不同的模块,比如一个简单的地址空间划分如下。CPU如果想访问UART模块,就需要保证发起的总线请求(如AHB请求)地址范围必须在0x4000_0000 - 0x4000_0FFF。

模块名称地址范围
DDR0x1000_0000 - 0x4000_0000
UART0x4000_0000 - 0x4000_0FFF
SPI0x4000_1000 - 0x4000_1FFF

0x4000_0000 - 0x4000_0FFF是分配给UART的地址空间,我们可以将这段地址空间分配给寄存器,MEM等等,且寄存器,MEM的起始地址,地址大小可以自由分配,只要保证不超过0x4000_0000 - 0x4000_0FFF这段地址空间。具体怎么分配需要由实际的业务决定。
寄存器表单中base_addr即表示寄存器的起始点,如下图寄存器从0x4000_8000开始。offset表示相对于base_addr的偏移。偏移量具体用多少位来表示,就记录在addr_sel信息中。上述寄存器表单中,addr_sel为16bit,对应offset中也用16bit来表示。
在这里插入图片描述
addr_width通常与SOC总线地址位宽一致,data_width通常与SOC总线数据位宽一致。
在UART_APB寄存器表单中data_width为32,那么APB请求访问uart_data寄存器时,一次性读写32bit,32bit占位4个地址空间。所以baud_div寄存器offset为0x0004
在这里插入图片描述

寄存器表单嵌套

上述例子中寄存器表单属于UART,如verilog中模块嵌套一样,寄存器表单也有嵌套的层次结构。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值