从零开始 verilog 以太网交换机(四)以太网转发表的设计与实现

从零开始 verilog 以太网交换机(四)以太网转发表的设计与实现



🔈声明:
😃博主主页:王_嘻嘻的CSDN主页
🧨 从零开始 verilog 以太网交换机系列专栏:点击这里
🔑未经作者允许,禁止转载,侵权必删
🚩关注本专题的朋友们可以收获一个经典交换机设计的全流程,包括设计与验证(FPGA);以太网MAC的基础知识。新手朋友们还将获得一个具有竞争力的项目经历,后续整个工程和代码下载链接也都会放在csdn和公众号内

  本章将开始进行以太网转发表的设计与实现,其负责判断数据帧对应的去向

  交换机完整的架构可以参考:从零开始 verilog 以太网交换机(一)架构分析


1、以太网转发表功能

  以太网转发表负责维护MAC地址和端口间的转发关系,需要经可能快速的帮助交换机确认数据帧应该从哪个端口发送出去。

  当交换机无法从转发表中得知去向时,需要向所有端口发送数据帧(也称作广播),这种操作将非常低效,发送过多无效数据占据带宽,所以逆向学习功能十分重要,转发表通过不断提取数据帧的源MAC地址和输入端口号,进行转发表的维护,便于后续的查找。

  另一方面,如果转发表内过多无效的逆向学习,将极大的浪费存储资源,所以需要设立一种老化机制,如果一个转发项过久没有使用,应该将它从表中剔除。

综上,以太网转发表需要满足快速查找逆向学习剔除老化项这三种功能。



为了帮助初学者理解,下图以一次简单的交换机处理为例来介绍转发表。

请添加图片描述

  1. 首先,设备A发送一个前往设备C的MAC帧,此时交换机的转发表为空;
  2. 转发表未查到对应的目的MAC地址信息,只能进行广播,将MAC帧发送到其余所有设备,交换机记录下设备A对应的端口号1,并记录一个TTL值(Time to Live,生存时间)
  3. 设备D发送一个前往设备A的MAC帧,此时交换机转发表中记录了设备A的端口号,交换机查找到结果,并将数据帧直接从Port1发送,并记录下设备D对应的端口号2,同时设置该条记录的TTL

①:在以太网交换机中,为了避免使用较少的转发表项占用过多的存储资源,需要为每条表项设置生存时间,转发表会定时扫描所有表项,并减少未使用表项的TTL,当TTL为0后,该表项将被剔除。


2、背景知识

  对于转发表电路快速查找的功能需求,通常有基于CAM(内容可寻址存储器)实现的查找电路基于hash散列的查找电路,下面我们将比较两种方案的特点,并选取一种作为本工程中转发表的实现方案。


2.1、基于CAM的查找表

  CAM是RAM(随机存取存储器)的一种延申,区别在于RAM输入的是存储单元的地址,返回存储单元的内容,而CAM输入的是关键字,输出的是内容与该关键字匹配的存储单元地址,以下有两种CAM的运用方式,读者可以感受一二。

请添加图片描述
  以上图为例,CAM中已经存在ff3b_227a、eb2c_991f、0000_0001、0001_0304,现在向CAM输入关键字eb2c_991f进行查找,对应的索引号为1。

  而存储单元内容可以有两种实现方案,一种是一并存储在CAM中,在关键字匹配后,将存储内容输出;第二种是将内容存储在RAM中,在关键字匹配并输出索引后,通过索引号去RAM对应地址中读取所需内容

  两种方案均有利有弊,方案一更为快速,时钟周期消耗小,但不够灵活,存储内容位宽变化会导致设计变化较大;方案二更为灵活,在需求变化时只需要重新生成RAM大小即可,但是其消耗的时钟周期更多


2.2、基于hash散列的查找电路

  另一种实现转发表快速查找的方案就是基于hash散列的查找电路,上述基于CAM的方案通常需要进行全局的遍历才能找到匹配结果,而hash散列可以先进行空间压缩,再在压缩后的空间内进行查找,可以极大的提升查找效率,这和软件中的hash散列原理一致。

  基于hash散列的查找流程如下:

  1. 根据关键字进行hash散列运算,得到hash值,该值和寻址空间直接相关
  2. 将1中计算得到的hash值为地址,从RAM中读出或写入关键字和所对应的信息,就完成了数据更新或转发信息的获取。

请添加图片描述

  在计算哈希值作为存放转发信息的地址时,必然会遇到不同关键字但计算出同一哈希值的情况,这种情况就叫做哈希冲突,通常处理哈希冲突的手段,是在冲突地址下使用链表的数据结构,当计算出冲突的哈希值后,再对该哈希值下的链表进行遍历,得到目标信息。

  不过在第一版的工程中,为了便于实现,我们使用一种双哈希桶的结构,也就是每个哈希值都有一个备份存储单元,这样保证了当哈希冲突不大于两次的时候,对转发信息不会有任何影响

  在第一版工程结束后,会设计一种更为合理的动态链表来解决哈希冲突


3、以太网转发表接口

  以太网转发表接口主要分为逆向学习/查询、老化清理两部分,具体接口如下。其中需要注意的是dut_source表示执行逆向学习或是查询功能。

在这里插入图片描述


4、以太网转发表实现细节


4.1、功能细节分析

  如上文所述,转发表需要完成的核心功能是根据MAC地址记录端口表项(逆向学习)、根据输入的hash值和MAC地址查询端口维护表项生存时间,并及时清理,防止信息无法写入。

  此外,为了防止运行过程中无效的X态影响查询,在初始化阶段需要对哈希桶进行初始化。



4.2、核心电路设计

请添加图片描述

  整个转发表仍然用状态机的方式进行实现,共分为8个状态, 4个部分组成。

  • CLEAN:哈希桶表项初始化阶段,对每个地址进行写‘0’操作;
  • LEARN、MATCH、MISMATCH:逆向学习阶段,读出hash值所指向的两个地址的表项,确认是否已经被占据,若仍有空间则选择一个存储最新的MAC地址和端口关系;若无空间存放则返回nack响应
  • SEARCH:搜索阶段,根据输入的hash值与MAC地址,查找对应的端口号,若查找成功则返回ack响应和对应端口号;若查找失败则返回nack响应
  • AGING:老化阶段,当没有学习或者查找请求时,对哈希桶进行扫描,每次对一个表项进行生存时间的更新,当生存时间为0后,清除表项


  当前设计的转发表设计仍然有较多缺点,不够灵活,只有双哈希桶的存放空间,且逆向学习和搜索是两种操作,需要进行两次请求

  后续有空后,会对该设计进一步优化,使用链表结构共享数据空间;将学习和搜索进行合并,当搜索失败且仍有空间时,会自动建立新的转发规则。


4.3、以太网转发表代码

  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;                   //哈希桶01和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干货分享!

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
实现一个简单的交换机,可以采用以下步骤: 1. 设计交换机的流水线结构,例如采用三级流水线:接收数据包、查找目的地址、转发数据包。 2. 实现交换机的输入输出端口,包括以太网端口和控制端口。 3. 设计交换机的查找表,用于存储MAC地址和对应的端口信息,可以采用SRAM或者CAM实现。 4. 实现交换机的流水线逻辑,包括接收数据包、查找目的地址、转发数据包等。 5. 测试交换机的性能和正确性,可以通过模拟数据包的发送和接收来进行测试。 以下是一个简单的Verilog代码示例,实现了一个具有4个以太网端口和1个控制端口的交换机: ``` module switch( input clk, input rst, input [47:0] in_data, // 输入数据包 input [3:0] in_port, // 输入端口 output reg [47:0] out_data, // 输出数据包 output reg [3:0] out_port, // 输出端口 input [47:0] mac_table [0:255], // MAC地址表 input [7:0] mac_port [0:255], // MAC地址对应的端口表 input [7:0] ctrl, // 控制端口 output reg [7:0] status // 状态输出 ); // 定义状态机状态 parameter IDLE = 0; parameter RECEIVE = 1; parameter FORWARD = 2; // 定义状态机变量 reg [1:0] state; reg [47:0] dest_mac; reg [3:0] dest_port; // 接收数据包 always @(posedge clk) begin if (rst) begin state <= IDLE; dest_mac <= 48'h000000000000; dest_port <= 4'h0; out_port <= 4'h0; out_data <= 48'h000000000000; status <= 8'h00; end else begin case(state) IDLE: begin if (in_port == ctrl) begin state <= RECEIVE; end end RECEIVE: begin dest_mac <= in_data[47:0]; state <= FORWARD; end FORWARD: begin if (mac_table[dest_mac[23:16]][47:0] == dest_mac) begin dest_port <= mac_port[dest_mac[23:16]][3:0]; end out_port <= dest_port; out_data <= in_data; status <= dest_port; state <= IDLE; end endcase end end endmodule ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值