马上就要开始找工作了,晚上睡觉的时候突然想在网上记录一下自己学习经历,我在知乎上学到了很多东西,感觉写点什么也是很好的成长机会.于是今天准备开此贴来记录一下我的手撕代码练习,在找到工作之前争取不断进步!
上学期观摩了一下师兄师姐的笔试题,然后这两天做实习笔试题又遇到了仲裁器代码手撕,或多或少有点零碎的记忆,今天就借此机会把这个问题搞定吧!
我的思路来自于这个微信公众号https://mp.weixin.qq.com/s?__biz=MzI3NjI5MjU3OQ==&mid=2247483852&idx=1&sn=31b59750c623be3d6a0e8c66eaecafb8&chksm=eb76f740dc017e56465f331cac5900c661b4ab52764a3c67c36e7e647fae7a606e8fa1ba77de&scene=21#wechat_redirect
我消化了一下并自己写了代码,如果有问题,还请指出和探讨.
固定优先级仲裁器
module Simplest_Para_FP_Arb#(
parameter N = 8
)(
input clk,
input rst_n,
input [ N-1:0 ] req,
output reg [ N-1:0 ] grant
);
wire [ N-1:0 ] gnt;
assign gnt = req & ~(req-1);
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
grant <= {N{1'b0}} ;
end
else begin
grant <= gnt ;
end
end
endmodule
固定优先级的本质是找到一个多比特信号中,最开始出现的1,一般是从低位到高位的顺序.
约定好:低位代表高优先级,高位代表低优先级
具体做法就是用到这个式子
req与它的2的补码按位与.即可得到
assign gnt = req & ~(req-1)
特性:一个数与它的补码相与,得到的是独热码.
(ps:这类一些列二进制数,找1的个数,找1的位置,我已经看见过我好几次手撕代码在问了,可能后续会开一期专门写一下?)
轮询仲裁器round-robin
其实说实话我最先开一直以为轮询算法的是选中某个从机时,那它的优先级降到最低,比他低的优先级加1,比它高的优先级不变,后来发现自己理解错了(不知道有没有我这种理解的仲裁器),round-robin还是要体现round, 值得是某一个被选中时,它的优先级被降为最低,而他旁边的那一位(我一般用高一位)的那个优先级变为最高.
先上代码再上思路.
`timescale 1ns / 1ns
module round_robin #(
parameter N = 8
)
(
input rst_n,
input clk,
input [N-1:0] req,
output [N-1:0] grant
);
reg [N-1:0] one_hot_priority; // 0010 => 1230 用独热码表示其优先级
reg [2*N-1:0] grant_double;
reg [N-1:0] one_hot_priority_q ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n) begin
one_hot_priority_q <= 8'b1;
end
else begin
one_hot_priority_q <= one_hot_priority;
end
end
//在每次仲裁之后,都要算出此时变化后的优先级,所以要用组合逻辑
always @(*)begin
if(!rst_n) begin
one_hot_priority = 8'b1;
end
else if (grant != 'd0)begin
one_hot_priority = {grant[N-2:0],grant[N-1]};
end
else begin
one_hot_priority = one_hot_priority_q;
end
end
//在下一个时钟周期输出,grant_double
always @(posedge clk or negedge rst_n)begin
if(!rst_n) begin
grant_double <= 16'b0;
end
else begin
grant_double <= {req,req} & ~({req,req}-{{N{1'b0}},one_hot_priority});
end
end
assign grant = grant_double[2*N-1:N] | grant_double[N-1:0];
endmodule
一个非常简单tb,其实我还不太会写tb.
`timescale 1ns / 1ns
module round_robin_tb();
reg rst_n;
reg clk;
reg [7:0] req;
reg empty_req;
wire [7:0] grant;
parameter N = 8;
initial begin
clk = 1'b0;
end
always #5 clk =~clk; //t=10ns
initial begin
rst_n = 1'b0;
# 95
rst_n = 1'b1;
end
initial begin
empty_req = 1'b0;
# 105
empty_req = 1'b1;
# 10
empty_req = 1'b0;
end
always @(posedge clk or rst_n) begin
if(~rst_n) begin
req <= {N{1'b0}};
end
else if(empty_req)begin
req <= 'b0;
end
else begin
req <= $random();
end
end
round_robin #( .N(N)
)
inst_round_robin (
.rst_n(rst_n),
.clk(clk),
.req(req),
.grant(grant)
);
endmodule
仿真波形图如下所示
我的思路是:
利用固定优先级 grant = req & ~( req-1)的改进法.
引申为:grant = req & ~( req-one_hot_priority)
出现一个问题: 这只能找到 在 priority 左边的 那个第一个1,但是找不到右边的. 也就是说req 不够减,会得到一个负数.
于是用req的两倍扩展来试试
double_grant = {req,req} & ~({req,req}-one_hot_priority);
当priority所在位和其左边的数有请求req时, 直接就可以找到其grant
当priority的左边的数没有对应的请求时, 只有在右边有,那么右边的值在复制过一边后,可以得到对应的响应
这样只需要double_grant的前面和后面一半相或既可
assign grant = grant_double[2*N-1:N] | grant_double[N-1:0];
而优先级更新只需将one_hot_priority循环位移1位即可.
在这里 double_grant 是req的打一拍后才得到的值,所以grant我用了assign 也相当于是grant,我们希望每次grant后都立马更新新的优先级, 这样就不会影响下一个req的判断, 所以更新优先级需要用组合逻辑, 这样的话遇到一个问题,组合逻辑不能赋值给自己保存状态, 如果有一个时钟周期的请求没有请求那该怎么办? 于是我想了个办法,给one_hot_priority寄存,保存它的值,在更新优先级的时候如果grant == 'd0;那么说明上一拍的req是0,这样就可以将优先级更新为之前打一拍保存后的优先级.
总结
其实固定优先级仲裁器知道一个grant = req & ~( req-1)
其实轮询仲裁器 就只需要知道grant = req & ~( req-1)并将其引申到grant = req & ~( req-one_hot_priority)再引申到 复制两遍
,这样就可以将优先级更新为之前打一拍保存后的优先级.
总结
其实固定优先级仲裁器知道一个grant = req & ~( req-1)
其实轮询仲裁器 就只需要知道grant = req & ~( req-1)并将其引申到grant = req & ~( req-one_hot_priority)再引申到 复制两遍
double_grant = {req,req} & ~({req,req}-one_hot_priority); 即可.剩下的打拍什么的就写写代码对着波形慢慢调咯?