声明:
博主主页:王_嘻嘻的CSDN主页
从零开始 verilog 以太网交换机系列专栏:点击这里
未经作者允许,禁止转载,侵权必删
关注本专题的朋友们可以收获一个经典交换机设计的全流程,包括设计与验证(FPGA);以太网MAC的基础知识。新手朋友们还将获得一个具有竞争力的项目经历,后续整个工程和代码下载链接也都会放在csdn和公众号内
本章将开始进行以太网转发表的设计与实现,其负责判断数据帧对应的去向。
交换机完整的架构可以参考:从零开始 verilog 以太网交换机(一)架构分析。
以太网转发表负责维护MAC地址和端口间的转发关系,需要经可能快速的帮助交换机确认数据帧应该从哪个端口发送出去。
当交换机无法从转发表中得知去向时,需要向所有端口发送数据帧(也称作广播),这种操作将非常低效,发送过多无效数据占据带宽,所以逆向学习功能十分重要,转发表通过不断提取数据帧的源MAC地址和输入端口号,进行转发表的维护,便于后续的查找。
另一方面,如果转发表内过多无效的逆向学习,将极大的浪费存储资源,所以需要设立一种老化机制,如果一个转发项过久没有使用,应该将它从表中剔除。
综上,以太网转发表需要满足快速查找、逆向学习、剔除老化项这三种功能。
为了帮助初学者理解,下图以一次简单的交换机处理为例来介绍转发表。
①:在以太网交换机中,为了避免使用较少的转发表项占用过多的存储资源,需要为每条表项设置生存时间,转发表会定时扫描所有表项,并减少未使用表项的TTL,当TTL为0后,该表项将被剔除。
对于转发表电路快速查找的功能需求,通常有基于CAM(内容可寻址存储器)实现的查找电路和基于hash散列的查找电路,下面我们将比较两种方案的特点,并选取一种作为本工程中转发表的实现方案。
CAM是RAM(随机存取存储器)的一种延申,区别在于RAM输入的是存储单元的地址,返回存储单元的内容,而CAM输入的是关键字,输出的是内容与该关键字匹配的存储单元地址,以下有两种CAM的运用方式,读者可以感受一二。
以上图为例,CAM中已经存在ff3b_227a、eb2c_991f、0000_0001、0001_0304,现在向CAM输入关键字eb2c_991f进行查找,对应的索引号为1。
而存储单元内容可以有两种实现方案,一种是一并存储在CAM中,在关键字匹配后,将存储内容输出;第二种是将内容存储在RAM中,在关键字匹配并输出索引后,通过索引号去RAM对应地址中读取所需内容。
两种方案均有利有弊,方案一更为快速,时钟周期消耗小,但不够灵活,存储内容位宽变化会导致设计变化较大;方案二更为灵活,在需求变化时只需要重新生成RAM大小即可,但是其消耗的时钟周期更多。
另一种实现转发表快速查找的方案就是基于hash散列的查找电路,上述基于CAM的方案通常需要进行全局的遍历才能找到匹配结果,而hash散列可以先进行空间压缩,再在压缩后的空间内进行查找,可以极大的提升查找效率,这和软件中的hash散列原理一致。
基于hash散列的查找流程如下:
在计算哈希值作为存放转发信息的地址时,必然会遇到不同关键字但计算出同一哈希值的情况,这种情况就叫做哈希冲突,通常处理哈希冲突的手段,是在冲突地址下使用链表的数据结构,当计算出冲突的哈希值后,再对该哈希值下的链表进行遍历,得到目标信息。
不过在第一版的工程中,为了便于实现,我们使用一种双哈希桶的结构,也就是每个哈希值都有一个备份存储单元,这样保证了当哈希冲突不大于两次的时候,对转发信息不会有任何影响。
在第一版工程结束后,会设计一种更为合理的动态链表来解决哈希冲突。
以太网转发表接口主要分为逆向学习/查询、老化清理两部分,具体接口如下。其中需要注意的是dut_source表示执行逆向学习或是查询功能。
如上文所述,转发表需要完成的核心功能是根据MAC地址记录端口表项(逆向学习)、根据输入的hash值和MAC地址查询端口、维护表项生存时间,并及时清理,防止信息无法写入。
此外,为了防止运行过程中无效的X态影响查询,在初始化阶段需要对哈希桶进行初始化。
整个转发表仍然用状态机的方式进行实现,共分为8个状态, 4个部分组成。
当前设计的转发表设计仍然有较多缺点,不够灵活,只有双哈希桶的存放空间,且逆向学习和搜索是两种操作,需要进行两次请求。
后续有空后,会对该设计进一步优化,使用链表结构共享数据空间;将学习和搜索进行合并,当搜索失败且仍有空间时,会自动建立新的转发规则。
控制器的设计并不复杂,Verilog代码将放在下面,Testbench就不展示了,有需要的可以等专题结束后在资源中下载,或者去我的公众号获得链接。
module port_lut(
input clk,
input rst_n,
input lut_type,
input [47:0] lut_mac,
input [15:0] lut_portmap,
input [9:0] lut_hash,
input lut_req,
output reg lut_ack,
output reg lut_nak,
output reg [15:0] lut_result,
input aging_req,
output reg aging_ack
);
//onehot encode
parameter IDLE = 8'h01;
parameter LEARN = 8'h2;
parameter MATCH = 8'h4;
parameter MISMATCH= 8'h8;
parameter SEARCH = 8'h10;
parameter AGING = 8'h20;
parameter DONE = 8'h40;
parameter CLEAN = 8'h80;
parameter TTL_NUM = 15'd2;
reg cfg_cleanup; //表项清除寄存器
reg clean_done; //表项清除完成标志
reg [7:0] cur_state;
reg [7:0] next_state;
reg [7:0] read_done; //type=1时,提前将表项读出,read done表述读取完毕标志
wire hit0; //哈希桶0、1和req是否匹配
wire hit1;
wire [14:0] item0_ttl;
wire [14:0] item1_ttl;
//ram0 as hash bucket0
reg ram0_en;
reg ram0_wr;
reg [9:0] ram0_addr;
reg [79:0] ram0_din;
wire [79:0] ram0_dout;
//ram1 as hash bucket1
reg ram1_en;
reg ram1_wr;
reg [9:0] ram1_addr;
reg [79:0] ram1_din;
wire [79:0] ram1_dout;
//req 采样
reg [47:0] req_mac_ff;
reg [15:0] req_portmap_ff;
reg [9:0] req_hash_ff;
//
reg [9:0] aging_addr;
//hash bucket结构
//[15:0] portmap
//[63:16] mac
//[78:64] TTL
//[79] valid
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
req_mac_ff[47:0] <= 48'b0;
req_portmap_ff[15:0] <= 16'b0;
req_hash_ff[9:0] <= 10'b0;
end
else if(lut_req)begin
req_mac_ff[47:0] <= lut_mac[47:0];
req_portmap_ff[15:0] <= lut_portmap[15:0];
req_hash_ff[9:0] <= lut_hash[9:0];
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cur_state[7:0] <= IDLE;
else
cur_state[7:0] <= next_state[7:0];
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cfg_cleanup <= 1'b1;
else if(cur_state[7:0]==CLEAN & next_state[7:0]==IDLE)
cfg_cleanup <= 1'b0;
end
always @(*)begin
case(cur_state[7:0])
IDLE: next_state[7:0] = cfg_cleanup ? CLEAN : (lut_req & lut_type & read_done) ? LEARN : (lut_req & !lut_type & read_done) ? SEARCH : (!lut_req & aging_req & read_done) ? AGING : IDLE;
LEARN: next_state[7:0] = (hit0 | hit1) ? MATCH : MISMATCH;
MATCH: next_state[7:0] = DONE;
MISMATCH: next_state[7:0] = DONE;
SEARCH: next_state[7:0] = DONE;
AGING: next_state[7:0] = DONE;
DONE: next_state[7:0] = IDLE;
CLEAN: next_state[7:0] = clean_done ? IDLE : CLEAN;
default: next_state[7:0] = IDLE;
endcase
end
//hash bucket control
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
clean_done <= 1'b1;
ram0_en <= 1'b0;
ram0_wr <= 1'b0;
ram0_addr[9:0] <= 10'b0;
ram0_din[79:0] <= 80'b0;
ram1_en <= 1'b0;
ram1_wr <= 1'b0;
ram1_addr[9:0] <= 10'b0;
ram1_din[79:0] <= 80'b0;
end
else begin
case(cur_state[7:0])
IDLE:begin
if(lut_req | aging_req)begin
ram0_en <= 1'b1;
ram0_wr <= 1'b0;
ram0_din[79:0] <= 80'b0;
ram0_addr[9:0] <= !clean_done ? 10'b0 : lut_req ? req_hash_ff[9:0] : (!lut_req & aging_req) ? aging_addr[9:0] : 10'b0;
ram1_en <= 1'b1;
ram1_wr <= 1'b0;
ram1_din[79:0] <= 80'b0;
ram1_addr[9:0] <= !clean_done ? 10'b0 : lut_req ? req_hash_ff[9:0] : (!lut_req & aging_req) ? aging_addr[9:0] : 10'b0;
end
else if(!clean_done)begin
ram0_en <= 1'b1;
ram0_wr <= 1'b1;
ram0_din[79:0] <= 80'b0;
ram1_en <= 1'b1;
ram1_wr <= 1'b1;
ram1_din[79:0] <= 80'b0;
end
end
MISMATCH:begin
case({ram1_dout[79],ram0_dout[79]})
2'b11:begin
ram0_en <= 1'b0;
ram1_en <= 1'b0;
end
2'b00,2'b10:begin
ram0_wr <= 1'b1;
ram0_din[79:0] <= {1'b1,TTL_NUM,req_mac_ff[47:0],req_portmap_ff[15:0]};
ram0_en <= 1'b1;
ram0_addr[9:0] <= req_hash_ff[9:0];
ram1_en <= 1'b0;
end
2'b01:begin
ram0_en <= 1'b0;
ram1_wr <= 1'b1;
ram1_en <= 1'b1;
ram1_din[79:0] <= {1'b1,TTL_NUM,req_mac_ff[47:0],req_portmap_ff[15:0]};
ram1_addr[9:0] <= req_hash_ff[9:0];
end
endcase
end
MATCH:begin
case({hit1,hit0})
2'b01:begin
ram0_wr <= 1'b1;
ram0_din[79:0] <= {1'b1,TTL_NUM,req_mac_ff[47:0],req_portmap_ff[15:0]};
ram0_en <= 1'b1;
ram0_addr[9:0] <= req_hash_ff[9:0];
ram1_en <= 1'b0;
end
2'b10:begin
ram0_en <= 1'b0;
ram1_wr <= 1'b1;
ram1_din[79:0] <= {1'b1,TTL_NUM,req_mac_ff[47:0],req_portmap_ff[15:0]};
ram1_en <= 1'b1;
ram1_addr[9:0] <= req_hash_ff[9:0];
end
default: begin
ram0_en <= 1'b0;
ram1_en <= 1'b0;
end
endcase
end
AGING:begin
ram0_din[79:0] <= item0_ttl[14:0]>15'b1 ? {1'b1,item0_ttl[14:0]-15'b1,ram0_dout[63:0]} : 80'b0;
ram0_en <= 1'b1;
ram0_wr <= 1'b1;
ram1_din[79:0] <= item1_ttl[14:0]>15'b1 ? {1'b1,item1_ttl[14:0]-15'b1,ram1_dout[63:0]} : 80'b0;
ram1_en <= 1'b1;
ram1_wr <= 1'b1;
end
DONE:begin
ram0_en <= 1'b0;
ram0_wr <= 1'b0;
ram0_addr[9:0] <= 10'b0;
ram0_din[79:0] <= 80'b0;
ram1_en <= 1'b0;
ram1_wr <= 1'b0;
ram1_addr[9:0] <= 10'b0;
ram1_din[79:0] <= 80'b0;
end
CLEAN:begin
ram0_en <= 1'b1;
ram0_wr <= 1'b1;
ram0_din[79:0] <= 80'b0;
ram0_addr[9:0] <= ram0_addr[9:0]<10'h3ff ? ram0_addr[9:0]+10'b1 : 10'b0;
ram1_en <= 1'b1;
ram1_wr <= 1'b1;
ram1_din[79:0] <= 80'b0;
ram1_addr[9:0] <= ram1_addr[9:0]<10'h3ff ? ram1_addr[9:0]+10'b1 : 10'b0;
end
default:begin
ram0_en <= 1'b0;
ram0_wr <= 1'b0;
ram0_addr[9:0] <= 10'b0;
ram0_din[79:0] <= 80'b0;
ram1_en <= 1'b0;
ram1_wr <= 1'b0;
ram1_addr[9:0] <= 10'b0;
ram1_din[79:0] <= 80'b0;
end
endcase
end
end
assign hit0 = (req_mac_ff[47:0]==ram0_dout[63:16]) & ram0_dout[79] & lut_req;
assign hit1 = (req_mac_ff[47:0]==ram1_dout[63:16]) & ram1_dout[79] & lut_req;
assign item0_ttl[14:0] = ram0_dout[78:64];
assign item1_ttl[14:0] = ram1_dout[78:64];
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
clean_done <= 1'b0;
// else if(!cur_state[7])
// clean_done <= 1'b0;
else if(cur_state[7] & ram0_addr[9:0]==10'h3ff)
clean_done <= 1'b1;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
read_done <= 1'b0;
else if(cur_state[7:0]!=IDLE)
read_done <= 1'b0;
else if(cur_state[7:0]==IDLE & ram0_en)
read_done <= 1'b1;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
aging_ack <= 1'b0;
else if(cur_state[7:0]==AGING)
aging_ack <= 1'b1;
else
aging_ack <= 1'b0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
lut_ack <= 1'b0;
else if(cur_state[7:0]==MATCH | ((cur_state[7:0]==MISMATCH) & ({ram0_dout[79],ram1_dout[79]}!=2'b11)) |(cur_state[7:0]==SEARCH & (hit0 | hit1)))
lut_ack <= 1'b1;
else
lut_ack <= 1'b0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
lut_nak <= 1'b0;
else if( ((cur_state[7:0]==MISMATCH) & (ram0_dout[79] & ram1_dout[79])) | (cur_state[7:0]==SEARCH & (!hit0 & !hit1)))
lut_nak <= 1'b1;
else
lut_nak <= 1'b0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
lut_result[15:0] <= 16'b0;
else if(cur_state[7:0]==SEARCH)begin
case({hit1,hit0})
2'b01: lut_result[15:0] <= ram0_dout[15:0];
2'b10: lut_result[15:0] <= ram1_dout[15:0];
default: lut_result[15:0] <= 16'b0;
endcase
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
aging_addr[9:0] <= 10'h0;
else if(aging_ack)
aging_addr[9:0] <= aging_addr[9:0] + 10'b1;
end
hash_bucket0 x_hash_bucket0 (
.clka(clk),
.ena(ram0_en),
.wea(ram0_wr),
.addra(ram0_addr[9:0]),
.dina(ram0_din[79:0]),
.douta(ram0_dout[79:0])
);
hash_bucket0 x_hash_bucket1 (
.clka(clk),
.ena(ram1_en),
.wea(ram1_wr),
.addra(ram1_addr[9:0]),
.dina(ram1_din[79:0]),
.douta(ram1_dout[79:0])
);
endmodule
搜索关注我的微信公众号【IC墨鱼仔】,获取我的更多IC干货分享!