当涉及多个模块向同一个模块进行读写操作、向一个半双工模块请求读写,甚至综合一下,多个模块向一个半双工模块发起读写请求,那就要涉及读写仲裁。因为最近做的项目中涉及的读写仲裁太多了,所以就想还是要写一个通用的读写仲裁模块,最好还是具备“凡请求,必执行”的功能的(因为一般简单实现的仲裁在发生冲突时,会选择执行一个,而直接忽视其他请求,这就对发起读写请求的模块的控制逻辑造成了不必要的麻烦),于是就有了这篇文章。
由于每个人实现的模块控制信号不尽相同,因此本文档中的代码仅作为一种实现思路的参考。下面以写仲裁作为例子介绍实现思路,读仲裁逻辑与之相同。
首先看怎么实现“凡请求,必执行”功能的,我们知道,若想将某些位进行置位,可以使用或运算,因此
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<CHANNEL_NUM; i=i+1) begin
if(W_CH_SEL[i]) begin
ch_index_W = i;
end
end
case(state_W)
SW_IDLE: begin
wr_req_o <= 1'b0;
wrdata_o <= 0;
wr_addr_o <= 0;
wr_busy <= wr_busy;
W_CH_SEL <= W_CH_SEL;
end
SW_ARB: begin
wr_busy <= wr_busy | wr_req; //将出现请求的通道标记
end
SW_POLL: begin
W_CH_SEL <= {W_CH_SEL[CHANNEL_NUM-2:0], W_CH_SEL[CHANNEL_NUM-1]}; //检查下一个通道
end
SW_WR: begin
wr_req_o <= 1'b1;
wrdata_o <= wrdata[ch_index_W*DATA_WIDTH +: DATA_WIDTH];
wr_addr_o <= wr_addr[ch_index_W*ADDR_WIDTH +: ADDR_WIDTH];
end
SW_WITE: begin
wr_req_o <= 1'b0;
end
SW_END: begin
wr_busy <= wr_busy & (~W_CH_SEL); //清除该通道的标记
end
default: begin
wr_req_o <= 1'b0;
wrdata_o <= 0;
wr_addr_o <= 0;
wr_busy <= wr_busy;
W_CH_SEL <= W_CH_SEL;
end
endcase
end
end
//------------------------Read Arbitrate---------------------------------
parameter SR_IDLE = 8'h01;
parameter SR_ARB = 8'h02; //仲裁
parameter SR_POLL = 8'h04; //轮询
parameter SR_RD = 8'h08;
parameter SR_WITE = 8'h10;
parameter SR_END = 8'h20;
reg [7:0] state_R = SR_IDLE;
reg [7:0] next_state_R;
reg [CHANNEL_NUM-1:0] R_CH_SEL = 1'b1; //选择哪个通道
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
state_R <= SR_IDLE;
end
else begin
state_R <= next_state_R;
end
end
always @(*) begin
case(state_R)
SR_IDLE: begin
next_state_R <= SR_ARB;
end
SR_ARB: begin
if(is_FULL_DUPLEX | (state_W == SW_POLL)) begin
if(|(rd_busy & R_CH_SEL)) begin //检测到该通道存在请求,启动读进程(rd_busy中记录了哪些通道出现了读请求,但还没有处理)
next_state_R <= SR_RD;
end
else begin
next_state_R <= SR_POLL; //否则轮询下个通道
end
end
else begin
next_state_R <= SR_POLL;
end
end
SR_POLL: begin
next_state_R <= SR_ARB;
end
SR_RD: begin
if(rd_busy_i) begin
next_state_R <= SR_WITE;
end
else begin
next_state_R <= SR_RD;
end
end
SR_WITE: begin
if(~rd_busy_i) begin
next_state_R <= SR_END;
end
else begin
next_state_R <= SR_WITE;
end
end
SR_END: begin
next_state_R <= SR_IDLE;
end
default: begin
next_state_R <= SR_IDLE;
end
endcase
end
integer ch_index_R;
integer j;
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
rd_req_o <= 1'b0;
rd_addr_o <= 0;
rddata <= 0;
rd_busy <= 0;
R_CH_SEL <= 1'b1;
end
else begin
ch_index_R = 0;
for(j=0; j<CHANNEL_NUM; j=j+1) begin
if(R_CH_SEL[j]) begin
ch_index_R = j;
end
end
case(state_R)
SR_IDLE: begin
rd_req_o <= 1'b0;
rd_addr_o <= 0;
rddata <= rddata;
rd_busy <= rd_busy;
R_CH_SEL <= R_CH_SEL;
end
SR_ARB: begin
rd_busy <= rd_busy | rd_req; //将出现请求的通道标记
end
SR_POLL: begin
R_CH_SEL <= {R_CH_SEL[CHANNEL_NUM-2:0], R_CH_SEL[CHANNEL_NUM-1]}; //检查下一个通道
end
SR_RD: begin
rd_req_o <= 1'b1;
rd_addr_o <= rd_addr[ch_index_R*ADDR_WIDTH +: ADDR_WIDTH];
end
SR_WITE: begin
rd_req_o <= 1'b0;
end
SR_END: begin
rddata[ch_index_R*ADDR_WIDTH +: ADDR_WIDTH] <= rddata_i;
rd_busy <= rd_busy & (~R_CH_SEL); //清除该通道的标记
end
default: begin
rd_req_o <= 1'b0;
rd_addr_o <= 0;
rddata <= rddata;
rd_busy <= rd_busy;
R_CH_SEL <= R_CH_SEL;
end
endcase
end
end
endmodule
测试代码 & 测试结果
`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
全双工时的结果如下:
半双工时的结果如下: