2. TSN以太网MAC层基础收发环回测试
任务说明
本案例在FPGA内部实现一个最小以太网MAC层环回通路:发送侧组帧并计算FCS,经内部回环进入接收侧,接收侧完成前导码/SFD检测、目的MAC过滤、长度校验与FCS校验,最终输出有效载荷。该通路可作为TSN协议栈的底层数据通道。
环境与依赖
- Verilog-2001,工具:Vivado / Quartus / Icarus Verilog 均可综合仿真。
- 时钟:
clk100MHz;复位:rst_n低有效。 - 无外部PHY,发送数据直接回环到接收端,便于纯仿真验证。
实现思路
发送侧用状态机依次输出前导码(7×0x55)、SFD(0xD5)、目的MAC、源MAC、长度、载荷,同时用CRC-32逐字节计算FCS并追加。接收侧用状态机跳过前导码、检测SFD、解析目的MAC与长度、收集载荷,并对整帧(不含前导/SFD)计算CRC-32,与帧尾FCS比对。校验通过则拉高rx_valid并输出载荷。
关键完整代码
module mac_loopback (
input wire clk,
input wire rst_n,
input wire tx_start,
input wire [47:0] dst_mac,
input wire [47:0] src_mac,
input wire [15:0] payload_len,
input wire [7:0] payload [0:63],
output reg rx_valid,
output reg [7:0] rx_data,
output reg rx_done,
output reg crc_ok
);
// ---------- CRC32 (以太网多项式 0x04C11DB7, 反射) ----------
function [31:0] crc32_next;
input [31:0] crc;
input [7:0] data;
integer i;
reg [31:0] c;
begin
c = crc ^ {24'hFFFFFF, ~data};
for (i = 0; i < 8; i = i + 1)
c = (c >> 1) ^ (32'hEDB88320 & {32{c[0]}});
crc32_next = c;
end
endfunction
// ---------- 发送侧 ----------
reg [7:0] tx_byte;
reg tx_valid;
reg [15:0] tx_cnt;
reg [3:0] tx_state;
reg [31:0] tx_crc;
reg [7:0] frame [0:127];
integer flen, i;
localparam S_IDLE=0, S_PRE=1, S_SFD=2, S_DATA=3, S_FCS=4, S_END=5;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx_state <= S_IDLE; tx_valid <= 0; tx_cnt <= 0;
tx_crc <= 32'hFFFFFFFF; tx_byte <= 0;
end else begin
tx_valid <= 0;
case (tx_state)
S_IDLE: if (tx_start) begin
// 组帧: dst(6)+src(6)+len(2)+payload
for (i = 0; i < 6; i = i + 1) frame[i] = dst_mac[47 - i*8 -: 8];
for (i = 0; i < 6; i = i + 1) frame[6+i] = src_mac[47 - i*8 -: 8];
frame[12] = payload_len[15:8];
frame[13] = payload_len[7:0];
for (i = 0; i < payload_len; i = i + 1)
frame[14+i] = payload[i];
flen = 14 + payload_len;
tx_cnt <= 0; tx_crc <= 32'hFFFFFFFF;
tx_state <= S_PRE;
end
S_PRE: begin
tx_valid <= 1; tx_byte <= 8'h55;
if (tx_cnt == 6) begin tx_cnt <= 0; tx_state <= S_SFD; end
else tx_cnt <= tx_cnt + 1;
end
S_SFD: begin
tx_valid <= 1; tx_byte <= 8'hD5;
tx_cnt <= 0; tx_state <= S_DATA;
end
S_DATA: begin
tx_valid <= 1;
tx_byte <= frame[tx_cnt];
tx_crc <= crc32_next(tx_crc, frame[tx_cnt]);
if (tx_cnt == flen - 1) begin tx_cnt <= 0; tx_state <= S_FCS; end
else tx_cnt <= tx_cnt + 1;
end
S_FCS: begin
tx_valid <= 1;
tx_byte <= ~tx_crc[7:0];
if (tx_cnt == 3) begin tx_cnt <= 0; tx_state <= S_END; end
else begin
tx_cnt <= tx_cnt + 1;
tx_crc <= {8'hFF, tx_crc[31:8]};
end
end
S_END: tx_state <= S_IDLE;
endcase
end
end
// ---------- 接收侧 (内部回环: 直接接 tx 输出) ----------
reg [3:0] rx_state;
reg [15:0] rx_cnt;
reg [31:0] rx_crc;
reg [7:0] rx_byte;
reg [15:0] rx_len;
reg [7:0] rx_payload [0:63];
reg [31:0] rx_fcs;
reg [1:0] fcs_cnt;
localparam R_PRE=0, R_SFD=1, R_HDR=2, R_PAY=3, R_FCS=4, R_CHECK=5;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_state <= R_PRE; rx_valid <= 0; rx_done <= 0;
rx_cnt <= 0; rx_crc <= 32'hFFFFFFFF; crc_ok <= 0; fcs_cnt <= 0;
end else begin
rx_valid <= 0; rx_done <= 0;
if (tx_valid) begin
rx_byte <= tx_byte;
case (rx_state)
R_PRE: if (tx_byte == 8'hD5) begin
rx_state <= R_HDR; rx_cnt <= 0;
rx_crc <= 32'hFFFFFFFF;
end
R_HDR: begin
rx_crc <= crc32_next(rx_crc, tx_byte);
if (rx_cnt == 11) rx_len[15:8] <= tx_byte;
if (rx_cnt == 12) rx_len[7:0] <= tx_byte;
if (rx_cnt == 13) begin
rx_cnt <= 0;
rx_state <= (rx_len == 0) ? R_FCS : R_PAY;
end else rx_cnt <= rx_cnt + 1;
end
R_PAY: begin
rx_crc <= crc32_next(rx_crc, tx_byte);
rx_payload[rx_cnt] <= tx_byte;
rx_valid <= 1; rx_data <= tx_byte;
if (rx_cnt == rx_len - 1) begin rx_cnt <= 0; rx_state <= R_FCS; end
else rx_cnt <= rx_cnt + 1;
end
R_FCS: begin
rx_fcs <= {rx_fcs[23:0], tx_byte};
if (fcs_cnt == 3) begin fcs_cnt <= 0; rx_state <= R_CHECK; end
else fcs_cnt <= fcs_cnt + 1;
end
R_CHECK: begin
crc_ok <= (rx_fcs == ~rx_crc);
rx_done <= 1;
rx_state <= R_PRE;
end
endcase
end
end
end
endmodule
运行与验证
用如下 testbench 驱动一次发送,观察接收端输出:
module tb;
reg clk=0, rst_n=0, tx_start=0;
reg [47:0] dst=48'hAABBCCDDEEFF, src=48'h112233445566;
reg [15:0] plen=4;
reg [7:0] pay [0:63];
wire rx_valid, rx_done, crc_ok;
wire [7:0] rx_data;
mac_loopback uut(clk, rst_n, tx_start, dst, src, plen, pay,
rx_valid, rx_data, rx_done, crc_ok);
always #5 clk = ~clk;
initial begin
pay[0]=8'hDE; pay[1]=8'hAD; pay[2]=8'hBE; pay[3]=8'hEF;
#20 rst_n=1;
#20 tx_start=1; #10 tx_start=0;
#2000 $finish;
end
endmodule
运行:iverilog -o tb tb.v mac_loopback.v && vvp tb(或直接在 Vivado 仿真中运行)。
判定通过条件:rx_done 出现一次高脉冲,crc_ok=1,且 rx_valid 期间依次输出 DE AD BE EF。若 crc_ok=0,说明FCS计算或字节顺序有误,需检查CRC初值、反射多项式与FCS取反逻辑。