题目描述
当A、B两组的信号请求访问某个模块时,为了保证正确的访问,需要对这些信号进行仲裁。请用Verilog实现一个仲裁器,对两组请求信号进行仲后,要求:
协议如图所示,请求方发送req(request)信号1表示有请求给仲裁器,仲裁器响应grant信号为1表示请求成功:
通过参数定义在冲突情形下,响应A/B的比例
(举例,一段时间内,有若干次A请求和若干次B请求,其中A&B发生冲突的有N次,这N次中先响应A 3次,后响应B 1次,循环反复。举例中的3和1可配置。);
添加必要的注释,增加代码可读性。
解题思路
根据题目描述,很容易想到模块arbiter的端口应该如下:
module arbiter (
input wire clk, // 时钟信号
input wire rst, // 复位信号
input wire reqA, // A组请求信号
input wire reqB, // B组请求信号
output reg grantA, // A组响应信号
output reg grantB // B组响应信号
);
时钟、复位信号。两个请求信号和两个相应响应信号。只有在冲突时,AB冲突响应比为3:1。其他情况下,正常响应。所以可以写一个计数器,仅在冲突时加1。计数器=0,1,2时响应A,计数器=3时响应B,于此同时将计数器置0。
对于仲裁部分,可以将总线的请求信号reqA和reqB拼接成一个2bit信号,这样使用case语句就能避免多级if-else嵌套导致的长组合逻辑链。
在case语句中,把{reqA, reqB}的所有可能2'b00,2'b01,2'b10,2'b11。全都规划到就行,当{reqA, reqB}==2'b11时,判断冲突计数器范围,落在[0,A_ratio-1],则冲突时A获得总线;若counter落在[A_ratio,A_ratio+B_ratio-2]时,冲突时B获得总线。这样模块满足题目要求可自定义冲突分配比例。
代码
module arbiter #(
parameter [7:0] A_ratio = 3 , // A grant ratio
parameter [7:0] B_ratio = 1 // B grant ratio
)(
input wire clk , // 时钟信号
input wire rstn , // 复位信号
input wire reqA , // A组请求信号
input wire reqB , // B组请求信号
output grantA , // A组响应信号
output grantB // B组响应信号
);
// 定义计数器和比例参数
reg [7:0] counter = 0; // belongs to [0, A_ratio + B_ratio - 1]
reg grantA_reg,grantB_reg;
assign grantA = grantA_reg;
assign grantB = grantB_reg;
always @(posedge clk) begin
if (!rstn) begin
grantA_reg <= 0;
grantB_reg <= 0;
end
else begin
case({reqA,reqB})
2'b00:begin
grantA_reg <= 1'b0;
grantB_reg <= 1'b0;
end
2'b01:begin
grantA_reg <= 1'b0;
grantB_reg <= 1'b1;
end
2'b10:begin
grantA_reg <= 1'b1;
grantB_reg <= 1'b0;
end
default:begin
if(counter <= (A_ratio - 1) )begin
grantA_reg <= 1'b1;
grantB_reg <= 1'b0;
end
else begin
grantA_reg <= 1'b0;
grantB_reg <= 1'b1;
end
end
endcase
end
end
always @(posedge clk)begin
if(!rstn)begin
counter <= 8'd0;
end
if( (reqA&&reqB) && (counter <= (A_ratio + B_ratio - 2)) )begin
counter <= counter + 1'b1;
end
else
counter <= 8'd0;
end
endmodule
tb
module arbiter_tb;
// 定义时钟和复位信号
reg clk;
reg rstn;
// 定义A组和B组请求信号
reg reqA;
reg reqB;
// 定义A组和B组响应信号
wire grantA;
wire grantB;
// 实例化被测试的模块
arbiter dut (
.clk(clk),
.rstn(rstn),
.reqA(reqA),
.reqB(reqB),
.grantA(grantA),
.grantB(grantB)
);
// 时钟信号发生器
always #5 clk = ~clk;
// 测试用例1:A组优先
initial begin
// 初始化信号
rstn = 0;
clk = 0;
reqA = 0;
reqB = 0;
// 复位
#15 rstn = 1;
#1
// 发送A组请求
#10 reqA = 1;
// 发送B组请求
#10 reqA = 0;reqB = 1;
// 发送A and B组请求 eight times
repeat(8) begin
#10 reqA = 1;reqB = 1;
end
repeat(2) begin
#10 reqA = 1;reqB = 0;
end
repeat(2) begin
#10 reqA = 0;reqB = 1;
end
#10 reqA = 0;reqB = 0;
// 停止测试
#100 $finish;
end
initial begin
$fsdbDumpfile("arbiter.fsdb");
$fsdbDumpvars(0);
end
endmodule
波形图
在tb里,先分别让A、B各请求总线一次,然后让他们出现请求冲突8次,最后再让A、B分别请求总线两次,从图中可以看到,在A、B请求冲突的时候,A_grant、B_grant拿到总线的比例是3:1,我们在module定义开头给了两个parameter,定义了A_grant、B_grant拿到总线的比例A_ratio和B_ratio,如果要修改模块代码,修改module传入的parameter的值即可。
更多手撕代码题可以前往 数字IC手撕代码--题库