通用读写仲裁模块(FPGA实现)

  当涉及多个模块向同一个模块进行读写操作、向一个半双工模块请求读写,甚至综合一下,多个模块向一个半双工模块发起读写请求,那就要涉及读写仲裁。因为最近做的项目中涉及的读写仲裁太多了,所以就想还是要写一个通用的读写仲裁模块,最好还是具备“凡请求,必执行”的功能的(因为一般简单实现的仲裁在发生冲突时,会选择执行一个,而直接忽视其他请求,这就对发起读写请求的模块的控制逻辑造成了不必要的麻烦),于是就有了这篇文章。

  由于每个人实现的模块控制信号不尽相同,因此本文档中的代码仅作为一种实现思路的参考。下面以写仲裁作为例子介绍实现思路,读仲裁逻辑与之相同。

  首先看怎么实现“凡请求,必执行”功能的,我们知道,若想将某些位进行置位,可以使用或运算,因此

SW_ARB: begin
    wr_busy		<= wr_busy | wr_req;		//将出现请求的通道标记
end

就可以将出现了写请求的通道标记,之后轮询时如果发现通道具有标记,则进入写功能流程。发现通道具有标记的代码如下

if(|(wr_busy & W_CH_SEL)) begin		//检测到该通道存在请求,启动写进程
    next_state_W	<= SW_WR;
end
else begin
    next_state_W	<= SW_POLL;		//否则轮询下个通道
end

首先将 wr_busy 和 W_CH_SEL 做与运算,则若当前通道存在标记,对应位将为 1,而若不存在标记,对应位为 0,其他非当前检查通道的位都为 0,之后再对与运算后的结果做一个或运算,就得到当前通道是否存在标记的判断值了。

  在进行完写操作后,需要将对应通道的标记清除,而我们也知道想要实现复位操作,可以使用与运算,因此

SW_END: begin
	wr_busy		<= wr_busy & (~W_CH_SEL);	//清除该通道的标记
end

就可以把对应通道的标记清除,以示写操作完成,而对应通道就可以决定是否再发起写请求了。

  轮询的实现,使用 CHANNEL_NUM 宽度的信号 W_CH_SEL 进行通道轮询,初始值置为 0b1,若检测到当前通道不存在请求标记,则轮询下一通道,实现方式为循环移位

SW_POLL: begin
	W_CH_SEL	<= {W_CH_SEL[CHANNEL_NUM-2:0], W_CH_SEL[CHANNEL_NUM-1]};	//检查下一个通道
end

  为了实现全双工时,读写过程的并发,因此读仲裁、写仲裁的状态机应该是独立的,分别维护自身的状态 state_W/state_R,而在半双工模式下,读写通道不能同时进入执行状态,因此需要判断对方的状态,且为了避免读写通道同时处于裁决状态时一起进入读写流程的问题,state_W 应当在检测到 state_R 处在轮询状态时才能进行裁决,对应的状态控制如下

SW_ARB: begin
    if(is_FULL_DUPLEX | (state_R == SR_POLL)) begin	//全双工,或者半双工且读通道正在通道轮询
        if(|(wr_busy & W_CH_SEL)) begin					//判断是否存在标记
            next_state_W	<= SW_WR;						//有标记,进入写进程
        end
        else begin
            next_state_W	<= SW_POLL;						//否则轮询
        end
    end
    else begin
        next_state_W	<= SW_ARB;					//半双工,且读通道正在裁决或执行读操作,则等待
    end
end

代码

/* 
 * file			: arb.v
 * author		: 今朝无言
 * date			: 2023-05-31
 * version		: v1.0
 * description	: 通用读写仲裁模块
 */
module arb(
input										clk,
input										rst_n,

//写通道仲裁
input		[CHANNEL_NUM-1:0]				wr_req,		//CHANNEL_NUM路写通道请求,高电平有效
input		[CHANNEL_NUM*DATA_WIDTH-1:0]	wrdata,		//对应的写数据
input		[CHANNEL_NUM*ADDR_WIDTH-1:0]	wr_addr,	//对应的写地址
output	reg	[CHANNEL_NUM-1:0]				wr_busy,	//回应各个通道是否写忙碌

output	reg									wr_req_o,	//输出到实际写入模块的写请求信号
output	reg	[DATA_WIDTH-1:0]				wrdata_o,	//对应的写数据
output	reg	[ADDR_WIDTH-1:0]				wr_addr_o,	//对应的写地址
input										wr_busy_i,	//模块是否忙碌

//读通道仲裁
input		[CHANNEL_NUM-1:0]				rd_req,		//多路读请求,高电平有效
output	reg	[CHANNEL_NUM*DATA_WIDTH-1:0]	rddata,		//对应的多路读数据
input		[CHANNEL_NUM*ADDR_WIDTH-1:0]	rd_addr,	//对应的读地址
output	reg	[CHANNEL_NUM-1:0]				rd_busy,	//回应各个通道是否读忙碌

output	reg									rd_req_o,	//输出到实际读模块的读请求信号
input		[DATA_WIDTH-1:0]				rddata_i,	//获取的数据
output	reg	[ADDR_WIDTH-1:0]				rd_addr_o,	//对应的读地址
input										rd_busy_i	//模块是否忙碌
);

parameter	is_FULL_DUPLEX 	= 1;	//是否全双工
parameter	CHANNEL_NUM		= 2;	//仲裁通道数
parameter	DATA_WIDTH		= 8;	//数据位宽
parameter	ADDR_WIDTH		= 8;	//地址位宽

//------------------------Write Arbitrate--------------------------------
parameter	SW_IDLE		= 8'h01;
parameter	SW_ARB		= 8'h02;	//仲裁
parameter	SW_POLL		= 8'h04;	//轮询
parameter	SW_WR		= 8'h08;
parameter	SW_WITE		= 8'h10;
parameter	SW_END		= 8'h20;

reg		[7:0]	state_W			= SW_IDLE;
reg		[7:0]	next_state_W;

reg		[CHANNEL_NUM-1:0]	W_CH_SEL	= 1'b1;	//选择哪个通道

always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		state_W		<= SW_IDLE;
	end
	else begin
		state_W		<= next_state_W;
	end
end

always @(*) begin
	case(state_W)
	SW_IDLE: begin
		next_state_W		<= SW_ARB;
	end
	SW_ARB: begin
		if(is_FULL_DUPLEX | (state_R == SR_POLL)) begin
			if(|(wr_busy & W_CH_SEL)) begin		//检测到该通道存在请求,启动写进程(wrbusy中记录了哪些通道出现了写请求,但还没有处理)
				next_state_W	<= SW_WR;
			end
			else begin
				next_state_W	<= SW_POLL;		//否则轮询下个通道
			end
		end
		else begin
			next_state_W	<= SW_ARB;
		end
	end
	SW_POLL: begin
		next_state_W		<= SW_ARB;
	end
	SW_WR: begin
		if(wr_busy_i) begin
			next_state_W	<= SW_WITE;
		end
		else begin
			next_state_W	<= SW_WR;
		end
	end
	SW_WITE: begin
		if(~wr_busy_i) begin
			next_state_W	<= SW_END;
		end
		else begin
			next_state_W	<= SW_WITE;
		end
	end
	SW_END: begin
		next_state_W		<= SW_IDLE;
	end
	default: begin
		next_state_W		<= SW_IDLE;
	end
	endcase
end

integer		ch_index_W;
integer		i;
always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		wr_req_o	<= 1'b0;
		wrdata_o	<= 0;
		wr_addr_o	<= 0;
		wr_busy		<= 0;
		W_CH_SEL	<= 1'b1;
	end
	else begin
		ch_index_W	= 0;
		for(i=0; i

测试代码 & 测试结果

`timescale 1ns/100ps

module arb_tb();

reg		clk_100M	= 1'b1;
always #5 begin
	clk_100M	<= ~clk_100M;
end

reg		rst_n;

reg				wr_req1, wr_req2, wr_req3;
reg		[7:0]	wrdata1, wrdata2, wrdata3;
reg		[7:0]	wr_addr1, wr_addr2, wr_addr3;
wire			wr_busy1, wr_busy2, wr_busy3;

wire			wr_req_o;
wire	[7:0]	wrdata_o;
wire	[7:0]	wr_addr_o;
reg				wr_busy_i;

reg				rd_req1, rd_req2, rd_req3;
wire	[7:0]	rddata1, rddata2, rddata3;
reg		[7:0]	rd_addr1, rd_addr2, rd_addr3;
wire			rd_busy1, rd_busy2, rd_busy3;

wire			rd_req_o;
reg		[7:0]	rddata_i;
wire	[7:0]	rd_addr_o;
reg				rd_busy_i;

//----------------------仲裁------------------------------------
arb #(
	.is_FULL_DUPLEX	(0),	//是否全双工
	.CHANNEL_NUM	(3),
	.DATA_WIDTH		(8),
	.ADDR_WIDTH 	(8)
)
arb_inst(
	.clk			(clk_100M),
	.rst_n			(rst_n),

	//写通道仲裁
	.wr_req			({wr_req1, wr_req2, wr_req3}),		//CHANNEL_NUM路写通道请求,高电平有效
	.wrdata			({wrdata1, wrdata2, wrdata3}),		//对应的写数据
	.wr_addr		({wr_addr1, wr_addr2, wr_addr3}),	//对应的写地址
	.wr_busy		({wr_busy1, wr_busy2, wr_busy3}),	//回应各个通道是否写忙碌

	.wr_req_o		(wr_req_o),							//输出到实际写入模块的写请求信号
	.wrdata_o		(wrdata_o),							//对应的写数据
	.wr_addr_o		(wr_addr_o),						//对应的写地址
	.wr_busy_i		(wr_busy_i),						//模块是否忙碌

	//读通道仲裁
	.rd_req			({rd_req1, rd_req2, rd_req3}),		//多路读请求,高电平有效
	.rddata			({rddata1, rddata2, rddata3}),		//对应的多路读数据
	.rd_addr		({rd_addr1, rd_addr2, rd_addr3}),	//对应的读地址
	.rd_busy		({rd_busy1, rd_busy2, rd_busy3}),	//回应各个通道是否读忙碌

	.rd_req_o		(rd_req_o),							//输出到实际读模块的读请求信号
	.rddata_i		(rddata_i),							//获取的数据
	.rd_addr_o		(rd_addr_o),						//对应的读地址
	.rd_busy_i		(rd_busy_i)							//模块是否忙碌
);

//-----------------------tb----------------------------------
localparam	WAIT_N	= 20;

wire	wr_req_o_pe;
reg		wr_req_o_d0;
reg		wr_req_o_d1;

wire	rd_req_o_pe;
reg		rd_req_o_d0;
reg		rd_req_o_d1;

always @(posedge clk_100M) begin
	wr_req_o_d0		<= wr_req_o;
	wr_req_o_d1		<= wr_req_o_d0;

	rd_req_o_d0		<= rd_req_o;
	rd_req_o_d1		<= rd_req_o_d0;
end

assign	wr_req_o_pe	= wr_req_o_d0 & (~wr_req_o_d1);
assign	rd_req_o_pe	= rd_req_o_d0 & (~rd_req_o_d1);

reg		[7:0]	wr_busy_cnt		= WAIT_N;
always @(posedge clk_100M) begin
	if(wr_req_o_pe) begin
		wr_busy_cnt		<= 8'd0;
	end
	else begin
		if(wr_busy_cnt <= WAIT_N) begin
			wr_busy_cnt		<= wr_busy_cnt + 1'b1;
		end
		else begin
			wr_busy_cnt		<= wr_busy_cnt;
		end
	end

	wr_busy_i		<= (wr_busy_cnt != 0 && wr_busy_cnt < WAIT_N)? 1'b1 : 1'b0;
end

reg		[7:0]	rd_busy_cnt		= WAIT_N;
always @(posedge clk_100M) begin
	if(rd_req_o_pe) begin
		rd_busy_cnt		<= 8'd0;
	end
	else begin
		if(rd_busy_cnt <= WAIT_N) begin
			rd_busy_cnt		<= rd_busy_cnt + 1'b1;
		end
		else begin
			rd_busy_cnt		<= rd_busy_cnt;
		end
	end

	rd_busy_i		<= (rd_busy_cnt != 0 && rd_busy_cnt < WAIT_N)? 1'b1 : 1'b0;

	rddata_i		<= rd_addr_o;
end

initial begin
	rst_n	<= 1'b0;
	{wr_req1, wr_req2, wr_req3}		<= 3'b000;
	{rd_req1, rd_req2, rd_req3}		<= 3'b000;
	#100;
	rst_n	<= 1'b1;
	#100;

	fork
		begin
			{wr_req1, wr_req2, wr_req3}		<= 3'b101;
			{wrdata1, wrdata2, wrdata3}		<= {8'd12, 8'd34, 8'd56};
			{wr_addr1, wr_addr2, wr_addr3}	<= {8'd1, 8'd2, 8'd3};
			#20;
			{wr_req1, wr_req2, wr_req3}		<= 3'b000;
			wait(wr_busy_i); wait(~wr_busy_i);
			wait(wr_busy_i); wait(~wr_busy_i);
			#100;
		end
		
		begin
			{rd_req1, rd_req2, rd_req3}		<= 3'b011;
			{rd_addr1, rd_addr2, rd_addr3}	<= {8'd1, 8'd2, 8'd3};
			#20;
			{rd_req1, rd_req2, rd_req3}		<= 3'b000;
			wait(rd_busy_i); wait(~rd_busy_i);
			wait(rd_busy_i); wait(~rd_busy_i);
			#100;
		end
	join

	$stop;
end

endmodule

全双工时的结果如下:

通用读写仲裁模块(FPGA实现)_第1张图片

半双工时的结果如下:

通用读写仲裁模块(FPGA实现)_第2张图片

你可能感兴趣的:(数字逻辑,fpga开发)