1
0
mirror of https://github.com/corundum/corundum.git synced 2025-01-16 08:12:53 +08:00

Add pause/PFC modules

Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
Alex Forencich 2023-07-23 16:31:33 -07:00
parent 6d5cda5986
commit ba5a883433
6 changed files with 1491 additions and 0 deletions

221
rtl/mac_pause_ctrl_rx.v Normal file
View File

@ -0,0 +1,221 @@
/*
Copyright (c) 2023 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Language: Verilog 2001
`resetall
`timescale 1ns / 1ps
`default_nettype none
/*
* PFC and pause frame receive handling
*/
module mac_pause_ctrl_rx #
(
parameter MCF_PARAMS_SIZE = 18,
parameter PFC_ENABLE = 1
)
(
input wire clk,
input wire rst,
/*
* MAC control frame interface
*/
input wire mcf_valid,
input wire [47:0] mcf_eth_dst,
input wire [47:0] mcf_eth_src,
input wire [15:0] mcf_eth_type,
input wire [15:0] mcf_opcode,
input wire [MCF_PARAMS_SIZE*8-1:0] mcf_params,
/*
* Link-level Flow Control (LFC) (IEEE 802.3 annex 31B PAUSE)
*/
input wire rx_lfc_en,
output wire rx_lfc_req,
input wire rx_lfc_ack,
/*
* Priority Flow Control (PFC) (IEEE 802.3 annex 31D PFC)
*/
input wire [7:0] rx_pfc_en,
output wire [7:0] rx_pfc_req,
input wire [7:0] rx_pfc_ack,
/*
* Configuration
*/
input wire [15:0] cfg_rx_lfc_opcode,
input wire cfg_rx_lfc_en,
input wire [15:0] cfg_rx_pfc_opcode,
input wire cfg_rx_pfc_en,
input wire [9:0] cfg_quanta_step,
input wire cfg_quanta_clk_en,
/*
* Status
*/
output wire stat_rx_lfc_pkt,
output wire stat_rx_lfc_xon,
output wire stat_rx_lfc_xoff,
output wire stat_rx_lfc_paused,
output wire stat_rx_pfc_pkt,
output wire [7:0] stat_rx_pfc_xon,
output wire [7:0] stat_rx_pfc_xoff,
output wire [7:0] stat_rx_pfc_paused
);
localparam QFB = 8;
// check configuration
initial begin
if (MCF_PARAMS_SIZE < (PFC_ENABLE ? 18 : 2)) begin
$error("Error: MCF_PARAMS_SIZE too small for requested configuration (instance %m)");
$finish;
end
end
reg lfc_req_reg = 1'b0, lfc_req_next;
reg [7:0] pfc_req_reg = 8'd0, pfc_req_next;
reg [16+QFB-1:0] lfc_quanta_reg = 0, lfc_quanta_next;
reg [16+QFB-1:0] pfc_quanta_reg[0:7], pfc_quanta_next[0:7];
reg stat_rx_lfc_pkt_reg = 1'b0, stat_rx_lfc_pkt_next;
reg stat_rx_lfc_xon_reg = 1'b0, stat_rx_lfc_xon_next;
reg stat_rx_lfc_xoff_reg = 1'b0, stat_rx_lfc_xoff_next;
reg stat_rx_pfc_pkt_reg = 1'b0, stat_rx_pfc_pkt_next;
reg [7:0] stat_rx_pfc_xon_reg = 0, stat_rx_pfc_xon_next;
reg [7:0] stat_rx_pfc_xoff_reg = 0, stat_rx_pfc_xoff_next;
assign rx_lfc_req = lfc_req_reg;
assign rx_pfc_req = pfc_req_reg;
assign stat_rx_lfc_pkt = stat_rx_lfc_pkt_reg;
assign stat_rx_lfc_xon = stat_rx_lfc_xon_reg;
assign stat_rx_lfc_xoff = stat_rx_lfc_xoff_reg;
assign stat_rx_lfc_paused = lfc_req_reg;
assign stat_rx_pfc_pkt = stat_rx_pfc_pkt_reg;
assign stat_rx_pfc_xon = stat_rx_pfc_xon_reg;
assign stat_rx_pfc_xoff = stat_rx_pfc_xoff_reg;
assign stat_rx_pfc_paused = pfc_req_reg;
integer k;
initial begin
for (k = 0; k < 8; k = k + 1) begin
pfc_quanta_reg[k] = 0;
end
end
always @* begin
stat_rx_lfc_pkt_next = 1'b0;
stat_rx_lfc_xon_next = 1'b0;
stat_rx_lfc_xoff_next = 1'b0;
stat_rx_pfc_pkt_next = 1'b0;
stat_rx_pfc_xon_next = 0;
stat_rx_pfc_xoff_next = 0;
if (cfg_quanta_clk_en && rx_lfc_ack) begin
if (lfc_quanta_reg > cfg_quanta_step) begin
lfc_quanta_next = lfc_quanta_reg - cfg_quanta_step;
end else begin
lfc_quanta_next = 0;
end
end else begin
lfc_quanta_next = lfc_quanta_reg;
end
lfc_req_next = (lfc_quanta_reg != 0) && rx_lfc_en && cfg_rx_lfc_en;
for (k = 0; k < 8; k = k + 1) begin
if (cfg_quanta_clk_en && rx_pfc_ack[k]) begin
if (pfc_quanta_reg[k] > cfg_quanta_step) begin
pfc_quanta_next[k] = pfc_quanta_reg[k] - cfg_quanta_step;
end else begin
pfc_quanta_next[k] = 0;
end
end else begin
pfc_quanta_next[k] = pfc_quanta_reg[k];
end
pfc_req_next[k] = (pfc_quanta_reg[k] != 0) && rx_pfc_en[k] && cfg_rx_pfc_en;
end
if (mcf_valid) begin
if (mcf_opcode == cfg_rx_lfc_opcode && cfg_rx_lfc_en) begin
stat_rx_lfc_pkt_next = 1'b1;
stat_rx_lfc_xon_next = {mcf_params[7:0], mcf_params[15:8]} == 0;
stat_rx_lfc_xoff_next = {mcf_params[7:0], mcf_params[15:8]} != 0;
lfc_quanta_next = {mcf_params[7:0], mcf_params[15:8], {QFB{1'b0}}};
end else if (PFC_ENABLE && mcf_opcode == cfg_rx_pfc_opcode && cfg_rx_pfc_en) begin
stat_rx_pfc_pkt_next = 1'b1;
for (k = 0; k < 8; k = k + 1) begin
if (mcf_params[k+8]) begin
stat_rx_pfc_xon_next[k] = {mcf_params[16+(k*16)+0 +: 8], mcf_params[16+(k*16)+8 +: 8]} == 0;
stat_rx_pfc_xoff_next[k] = {mcf_params[16+(k*16)+0 +: 8], mcf_params[16+(k*16)+8 +: 8]} != 0;
pfc_quanta_next[k] = {mcf_params[16+(k*16)+0 +: 8], mcf_params[16+(k*16)+8 +: 8], {QFB{1'b0}}};
end
end
end
end
end
always @(posedge clk) begin
lfc_req_reg <= lfc_req_next;
pfc_req_reg <= pfc_req_next;
lfc_quanta_reg <= lfc_quanta_next;
for (k = 0; k < 8; k = k + 1) begin
pfc_quanta_reg[k] <= pfc_quanta_next[k];
end
stat_rx_lfc_pkt_reg <= stat_rx_lfc_pkt_next;
stat_rx_lfc_xon_reg <= stat_rx_lfc_xon_next;
stat_rx_lfc_xoff_reg <= stat_rx_lfc_xoff_next;
stat_rx_pfc_pkt_reg <= stat_rx_pfc_pkt_next;
stat_rx_pfc_xon_reg <= stat_rx_pfc_xon_next;
stat_rx_pfc_xoff_reg <= stat_rx_pfc_xoff_next;
if (rst) begin
lfc_req_reg <= 1'b0;
pfc_req_reg <= 8'd0;
lfc_quanta_reg <= 0;
for (k = 0; k < 8; k = k + 1) begin
pfc_quanta_reg[k] <= 0;
end
stat_rx_lfc_pkt_reg <= 1'b0;
stat_rx_lfc_xon_reg <= 1'b0;
stat_rx_lfc_xoff_reg <= 1'b0;
stat_rx_pfc_pkt_reg <= 1'b0;
stat_rx_pfc_xon_reg <= 0;
stat_rx_pfc_xoff_reg <= 0;
end
end
endmodule
`resetall

313
rtl/mac_pause_ctrl_tx.v Normal file
View File

@ -0,0 +1,313 @@
/*
Copyright (c) 2023 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Language: Verilog 2001
`resetall
`timescale 1ns / 1ps
`default_nettype none
/*
* PFC and pause frame transmit handling
*/
module mac_pause_ctrl_tx #
(
parameter MCF_PARAMS_SIZE = 18,
parameter PFC_ENABLE = 1
)
(
input wire clk,
input wire rst,
/*
* MAC control frame interface
*/
output wire mcf_valid,
input wire mcf_ready,
output wire [47:0] mcf_eth_dst,
output wire [47:0] mcf_eth_src,
output wire [15:0] mcf_eth_type,
output wire [15:0] mcf_opcode,
output wire [MCF_PARAMS_SIZE*8-1:0] mcf_params,
/*
* Link-level Flow Control (LFC) (IEEE 802.3 annex 31B PAUSE)
*/
input wire tx_lfc_req,
input wire tx_lfc_resend,
/*
* Priority Flow Control (PFC) (IEEE 802.3 annex 31D)
*/
input wire [7:0] tx_pfc_req,
input wire tx_pfc_resend,
/*
* Configuration
*/
input wire [47:0] cfg_tx_lfc_eth_dst,
input wire [47:0] cfg_tx_lfc_eth_src,
input wire [15:0] cfg_tx_lfc_eth_type,
input wire [15:0] cfg_tx_lfc_opcode,
input wire cfg_tx_lfc_en,
input wire [15:0] cfg_tx_lfc_quanta,
input wire [15:0] cfg_tx_lfc_refresh,
input wire [47:0] cfg_tx_pfc_eth_dst,
input wire [47:0] cfg_tx_pfc_eth_src,
input wire [15:0] cfg_tx_pfc_eth_type,
input wire [15:0] cfg_tx_pfc_opcode,
input wire cfg_tx_pfc_en,
input wire [8*16-1:0] cfg_tx_pfc_quanta,
input wire [8*16-1:0] cfg_tx_pfc_refresh,
input wire [9:0] cfg_quanta_step,
input wire cfg_quanta_clk_en,
/*
* Status
*/
output wire stat_tx_lfc_pkt,
output wire stat_tx_lfc_xon,
output wire stat_tx_lfc_xoff,
output wire stat_tx_lfc_paused,
output wire stat_tx_pfc_pkt,
output wire [7:0] stat_tx_pfc_xon,
output wire [7:0] stat_tx_pfc_xoff,
output wire [7:0] stat_tx_pfc_paused
);
localparam QFB = 8;
// check configuration
initial begin
if (MCF_PARAMS_SIZE < (PFC_ENABLE ? 18 : 2)) begin
$error("Error: MCF_PARAMS_SIZE too small for requested configuration (instance %m)");
$finish;
end
end
reg lfc_req_reg = 1'b0, lfc_req_next;
reg lfc_act_reg = 1'b0, lfc_act_next;
reg lfc_send_reg = 1'b0, lfc_send_next;
reg [7:0] pfc_req_reg = 8'd0, pfc_req_next;
reg [7:0] pfc_act_reg = 8'd0, pfc_act_next;
reg [7:0] pfc_en_reg = 8'd0, pfc_en_next;
reg pfc_send_reg = 1'b0, pfc_send_next;
reg [16+QFB-1:0] lfc_refresh_reg = 0, lfc_refresh_next;
reg [16+QFB-1:0] pfc_refresh_reg[0:7], pfc_refresh_next[0:7];
reg stat_tx_lfc_pkt_reg = 1'b0, stat_tx_lfc_pkt_next;
reg stat_tx_lfc_xon_reg = 1'b0, stat_tx_lfc_xon_next;
reg stat_tx_lfc_xoff_reg = 1'b0, stat_tx_lfc_xoff_next;
reg stat_tx_pfc_pkt_reg = 1'b0, stat_tx_pfc_pkt_next;
reg [7:0] stat_tx_pfc_xon_reg = 0, stat_tx_pfc_xon_next;
reg [7:0] stat_tx_pfc_xoff_reg = 0, stat_tx_pfc_xoff_next;
// MAC control interface
reg mcf_pfc_sel_reg = PFC_ENABLE != 0, mcf_pfc_sel_next;
reg mcf_valid_reg = 1'b0, mcf_valid_next;
wire [2*8-1:0] mcf_lfc_params;
assign mcf_lfc_params[16*0 +: 16] = lfc_req_reg ? {cfg_tx_lfc_quanta[0 +: 8], cfg_tx_lfc_quanta[8 +: 8]} : 0;
wire [18*8-1:0] mcf_pfc_params;
assign mcf_pfc_params[16*0 +: 16] = {pfc_en_reg, 8'd0};
assign mcf_pfc_params[16*1 +: 16] = pfc_req_reg[0] ? {cfg_tx_pfc_quanta[16*0+0 +: 8], cfg_tx_pfc_quanta[16*0+8 +: 8]} : 0;
assign mcf_pfc_params[16*2 +: 16] = pfc_req_reg[1] ? {cfg_tx_pfc_quanta[16*1+0 +: 8], cfg_tx_pfc_quanta[16*1+8 +: 8]} : 0;
assign mcf_pfc_params[16*3 +: 16] = pfc_req_reg[2] ? {cfg_tx_pfc_quanta[16*2+0 +: 8], cfg_tx_pfc_quanta[16*2+8 +: 8]} : 0;
assign mcf_pfc_params[16*4 +: 16] = pfc_req_reg[3] ? {cfg_tx_pfc_quanta[16*3+0 +: 8], cfg_tx_pfc_quanta[16*3+8 +: 8]} : 0;
assign mcf_pfc_params[16*5 +: 16] = pfc_req_reg[4] ? {cfg_tx_pfc_quanta[16*4+0 +: 8], cfg_tx_pfc_quanta[16*4+8 +: 8]} : 0;
assign mcf_pfc_params[16*6 +: 16] = pfc_req_reg[5] ? {cfg_tx_pfc_quanta[16*5+0 +: 8], cfg_tx_pfc_quanta[16*5+8 +: 8]} : 0;
assign mcf_pfc_params[16*7 +: 16] = pfc_req_reg[6] ? {cfg_tx_pfc_quanta[16*6+0 +: 8], cfg_tx_pfc_quanta[16*6+8 +: 8]} : 0;
assign mcf_pfc_params[16*8 +: 16] = pfc_req_reg[7] ? {cfg_tx_pfc_quanta[16*7+0 +: 8], cfg_tx_pfc_quanta[16*7+8 +: 8]} : 0;
assign mcf_valid = mcf_valid_reg;
assign mcf_eth_dst = (PFC_ENABLE && mcf_pfc_sel_reg) ? cfg_tx_pfc_eth_dst : cfg_tx_lfc_eth_dst;
assign mcf_eth_src = (PFC_ENABLE && mcf_pfc_sel_reg) ? cfg_tx_pfc_eth_src : cfg_tx_lfc_eth_src;
assign mcf_eth_type = (PFC_ENABLE && mcf_pfc_sel_reg) ? cfg_tx_pfc_eth_type : cfg_tx_lfc_eth_type;
assign mcf_opcode = (PFC_ENABLE && mcf_pfc_sel_reg) ? cfg_tx_pfc_opcode : cfg_tx_lfc_opcode;
assign mcf_params = (PFC_ENABLE && mcf_pfc_sel_reg) ? mcf_pfc_params : mcf_lfc_params;
assign stat_tx_lfc_pkt = stat_tx_lfc_pkt_reg;
assign stat_tx_lfc_xon = stat_tx_lfc_xon_reg;
assign stat_tx_lfc_xoff = stat_tx_lfc_xoff_reg;
assign stat_tx_lfc_paused = lfc_req_reg;
assign stat_tx_pfc_pkt = stat_tx_pfc_pkt_reg;
assign stat_tx_pfc_xon = stat_tx_pfc_xon_reg;
assign stat_tx_pfc_xoff = stat_tx_pfc_xoff_reg;
assign stat_tx_pfc_paused = pfc_req_reg;
integer k;
initial begin
for (k = 0; k < 8; k = k + 1) begin
pfc_refresh_reg[k] = 0;
end
end
always @* begin
lfc_req_next = lfc_req_reg;
lfc_act_next = lfc_act_reg;
lfc_send_next = lfc_send_reg | tx_lfc_resend;
pfc_req_next = pfc_req_reg;
pfc_act_next = pfc_act_reg;
pfc_en_next = pfc_en_reg;
pfc_send_next = pfc_send_reg | tx_pfc_resend;
mcf_pfc_sel_next = mcf_pfc_sel_reg;
mcf_valid_next = mcf_valid_reg && !mcf_ready;
stat_tx_lfc_pkt_next = 1'b0;
stat_tx_lfc_xon_next = 1'b0;
stat_tx_lfc_xoff_next = 1'b0;
stat_tx_pfc_pkt_next = 1'b0;
stat_tx_pfc_xon_next = 0;
stat_tx_pfc_xoff_next = 0;
if (cfg_quanta_clk_en) begin
if (lfc_refresh_reg > cfg_quanta_step) begin
lfc_refresh_next = lfc_refresh_reg - cfg_quanta_step;
end else begin
lfc_refresh_next = 0;
if (lfc_req_reg) begin
lfc_send_next = 1'b1;
end
end
end else begin
lfc_refresh_next = lfc_refresh_reg;
end
for (k = 0; k < 8; k = k + 1) begin
if (cfg_quanta_clk_en) begin
if (pfc_refresh_reg[k] > cfg_quanta_step) begin
pfc_refresh_next[k] = pfc_refresh_reg[k] - cfg_quanta_step;
end else begin
pfc_refresh_next[k] = 0;
if (pfc_req_reg[k]) begin
pfc_send_next = 1'b1;
end
end
end else begin
pfc_refresh_next[k] = pfc_refresh_reg[k];
end
end
if (cfg_tx_lfc_en) begin
if (!mcf_valid_reg) begin
if (lfc_req_reg != tx_lfc_req) begin
lfc_req_next = tx_lfc_req;
lfc_act_next = lfc_act_reg | tx_lfc_req;
lfc_send_next = 1'b1;
end
if (lfc_send_reg && !(PFC_ENABLE && cfg_tx_pfc_en && pfc_send_reg)) begin
mcf_pfc_sel_next = 1'b0;
mcf_valid_next = lfc_act_reg;
lfc_act_next = lfc_req_reg;
lfc_refresh_next = lfc_req_reg ? {cfg_tx_lfc_refresh, {QFB{1'b0}}} : 0;
lfc_send_next = 1'b0;
stat_tx_lfc_pkt_next = lfc_act_reg;
stat_tx_lfc_xon_next = lfc_act_reg && !lfc_req_reg;
stat_tx_lfc_xoff_next = lfc_act_reg && lfc_req_reg;
end
end
end
if (PFC_ENABLE && cfg_tx_pfc_en) begin
if (!mcf_valid_reg) begin
if (pfc_req_reg != tx_pfc_req) begin
pfc_req_next = tx_pfc_req;
pfc_act_next = pfc_act_reg | tx_pfc_req;
pfc_send_next = 1'b1;
end
if (pfc_send_reg) begin
mcf_pfc_sel_next = 1'b1;
mcf_valid_next = pfc_act_reg != 0;
pfc_en_next = pfc_act_reg;
pfc_act_next = pfc_req_reg;
for (k = 0; k < 8; k = k + 1) begin
pfc_refresh_next[k] = pfc_req_reg[k] ? {cfg_tx_pfc_refresh[16*k +: 16], {QFB{1'b0}}} : 0;
end
pfc_send_next = 1'b0;
stat_tx_pfc_pkt_next = pfc_act_reg != 0;
stat_tx_pfc_xon_next = pfc_act_reg & ~pfc_req_reg;
stat_tx_pfc_xoff_next = pfc_act_reg & pfc_req_reg;
end
end
end
end
always @(posedge clk) begin
lfc_req_reg <= lfc_req_next;
lfc_act_reg <= lfc_act_next;
lfc_send_reg <= lfc_send_next;
pfc_req_reg <= pfc_req_next;
pfc_act_reg <= pfc_act_next;
pfc_en_reg <= pfc_en_next;
pfc_send_reg <= pfc_send_next;
mcf_pfc_sel_reg <= mcf_pfc_sel_next;
mcf_valid_reg <= mcf_valid_next;
lfc_refresh_reg <= lfc_refresh_next;
for (k = 0; k < 8; k = k + 1) begin
pfc_refresh_reg[k] <= pfc_refresh_next[k];
end
stat_tx_lfc_pkt_reg <= stat_tx_lfc_pkt_next;
stat_tx_lfc_xon_reg <= stat_tx_lfc_xon_next;
stat_tx_lfc_xoff_reg <= stat_tx_lfc_xoff_next;
stat_tx_pfc_pkt_reg <= stat_tx_pfc_pkt_next;
stat_tx_pfc_xon_reg <= stat_tx_pfc_xon_next;
stat_tx_pfc_xoff_reg <= stat_tx_pfc_xoff_next;
if (rst) begin
lfc_req_reg <= 1'b0;
lfc_act_reg <= 1'b0;
lfc_send_reg <= 1'b0;
pfc_req_reg <= 0;
pfc_act_reg <= 0;
pfc_send_reg <= 0;
mcf_pfc_sel_reg <= PFC_ENABLE != 0;
mcf_valid_reg <= 1'b0;
lfc_refresh_reg <= 0;
for (k = 0; k < 8; k = k + 1) begin
pfc_refresh_reg[k] <= 0;
end
stat_tx_lfc_pkt_reg <= 1'b0;
stat_tx_lfc_xon_reg <= 1'b0;
stat_tx_lfc_xoff_reg <= 1'b0;
stat_tx_pfc_pkt_reg <= 1'b0;
stat_tx_pfc_xon_reg <= 0;
stat_tx_pfc_xoff_reg <= 0;
end
end
endmodule
`resetall

View File

@ -0,0 +1,69 @@
# Copyright (c) 2023 Alex Forencich
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
TOPLEVEL_LANG = verilog
SIM ?= icarus
WAVES ?= 0
COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ps
DUT = mac_pause_ctrl_rx
TOPLEVEL = $(DUT)
MODULE = test_$(DUT)
VERILOG_SOURCES += ../../rtl/$(DUT).v
# module parameters
export PARAM_MCF_PARAMS_SIZE := 18
export PARAM_PFC_ENABLE := 1
ifeq ($(SIM), icarus)
PLUSARGS += -fst
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v:
echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
echo 'end' >> $@
echo 'endmodule' >> $@
clean::
@rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst

View File

@ -0,0 +1,424 @@
#!/usr/bin/env python
"""
Copyright (c) 2023 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import logging
import os
import struct
from scapy.layers.l2 import Ether
from scapy.utils import mac2str
import cocotb_test.simulator
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge
from cocotb.regression import TestFactory
from cocotb.utils import get_sim_time
from cocotbext.axi.stream import define_stream
McfBus, McfTransaction, McfSource, McfSink, McfMonitor = define_stream("Mcf",
signals=["valid", "eth_dst", "eth_src", "eth_type", "opcode", "params"],
optional_signals=["ready", "id", "dest", "user"]
)
class TB:
def __init__(self, dut):
self.dut = dut
self.log = logging.getLogger("cocotb.tb")
self.log.setLevel(logging.DEBUG)
cocotb.start_soon(Clock(dut.clk, 6.4, units="ns").start())
self.mcf_source = McfSource(McfBus.from_prefix(dut, "mcf"), dut.clk, dut.rst)
dut.rx_lfc_en.setimmediatevalue(0)
dut.rx_lfc_ack.setimmediatevalue(0)
dut.rx_pfc_en.setimmediatevalue(0)
dut.rx_pfc_ack.setimmediatevalue(0)
dut.cfg_rx_lfc_opcode.setimmediatevalue(0)
dut.cfg_rx_lfc_en.setimmediatevalue(0)
dut.cfg_rx_pfc_opcode.setimmediatevalue(0)
dut.cfg_rx_pfc_en.setimmediatevalue(0)
dut.cfg_quanta_step.setimmediatevalue(256)
dut.cfg_quanta_clk_en.setimmediatevalue(1)
async def reset(self):
self.dut.rst.setimmediatevalue(0)
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
self.dut.rst.value = 1
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
self.dut.rst.value = 0
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
async def send_mcf(self, pkt):
mcf = McfTransaction()
mcf.eth_dst = int.from_bytes(mac2str(pkt[Ether].dst), 'big')
mcf.eth_src = int.from_bytes(mac2str(pkt[Ether].src), 'big')
mcf.eth_type = pkt[Ether].type
mcf.opcode = int.from_bytes(bytes(pkt[Ether].payload)[0:2], 'big')
mcf.params = int.from_bytes(bytes(pkt[Ether].payload)[2:], 'little')
await self.mcf_source.send(mcf)
async def run_test_lfc(dut):
tb = TB(dut)
await tb.reset()
dut.rx_lfc_en.value = 1
dut.rx_lfc_ack.value = 0
dut.rx_pfc_en.value = 0
dut.rx_pfc_ack.value = 0
dut.cfg_rx_lfc_opcode.value = 0x0001
dut.cfg_rx_lfc_en.value = 1
dut.cfg_rx_pfc_opcode.value = 0x0101
dut.cfg_rx_pfc_en.value = 0
dut.cfg_quanta_step.value = int(10000*256 / (512*156.25))
dut.cfg_quanta_clk_en.value = 1
tb.log.info("Test release time accuracy")
eth = Ether(src='5A:51:52:53:54:55', dst='01:80:C2:00:00:01', type=0x8808)
test_pkt = eth / struct.pack('!HH', 0x0001, 100)
await tb.send_mcf(test_pkt)
while dut.rx_lfc_req.value == 0:
await RisingEdge(dut.clk)
dut.rx_lfc_ack.value = 1
start_time = get_sim_time('sec')
while dut.rx_lfc_req.value:
await RisingEdge(dut.clk)
stop_time = get_sim_time('sec')
dut.rx_lfc_ack.value = 0
pause_time = stop_time-start_time
pause_quanta = pause_time / (512 * 1/10e9)
tb.log.info("pause time : %g s", pause_time)
tb.log.info("pause quanta : %f", pause_quanta)
assert round(pause_quanta) == 100
tb.log.info("Test release time accuracy (with refresh)")
eth = Ether(src='5A:51:52:53:54:55', dst='01:80:C2:00:00:01', type=0x8808)
test_pkt = eth / struct.pack('!HH', 0x0001, 100)
await tb.send_mcf(test_pkt)
while dut.rx_lfc_req.value == 0:
await RisingEdge(dut.clk)
dut.rx_lfc_ack.value = 1
for k in range(400):
await RisingEdge(dut.clk)
await tb.send_mcf(test_pkt)
start_time = get_sim_time('sec')
while dut.rx_lfc_req.value:
await RisingEdge(dut.clk)
stop_time = get_sim_time('sec')
dut.rx_lfc_ack.value = 0
pause_time = stop_time-start_time
pause_quanta = pause_time / (512 * 1/10e9)
tb.log.info("pause time : %g s", pause_time)
tb.log.info("pause quanta : %f", pause_quanta)
assert round(pause_quanta) == 100
tb.log.info("Test explicit release")
eth = Ether(src='5A:51:52:53:54:55', dst='01:80:C2:00:00:01', type=0x8808)
test_pkt = eth / struct.pack('!HH', 0x0001, 100)
await tb.send_mcf(test_pkt)
while dut.rx_lfc_req.value == 0:
await RisingEdge(dut.clk)
dut.rx_lfc_ack.value = 1
start_time = get_sim_time('sec')
eth = Ether(src='5A:51:52:53:54:55', dst='01:80:C2:00:00:01', type=0x8808)
test_pkt = eth / struct.pack('!HH', 0x0001, 0)
await tb.send_mcf(test_pkt)
while dut.rx_lfc_req.value:
await RisingEdge(dut.clk)
stop_time = get_sim_time('sec')
dut.rx_lfc_ack.value = 0
pause_time = stop_time-start_time
pause_quanta = pause_time / (512 * 1/10e9)
tb.log.info("pause time : %g s", pause_time)
tb.log.info("pause quanta : %f", pause_quanta)
assert round(pause_quanta) < 50
await RisingEdge(dut.clk)
await RisingEdge(dut.clk)
async def run_test_pfc(dut):
tb = TB(dut)
await tb.reset()
dut.rx_lfc_en.value = 0
dut.rx_lfc_ack.value = 0
dut.rx_pfc_en.value = 0xFF
dut.rx_pfc_ack.value = 0
dut.cfg_rx_lfc_opcode.value = 0x0001
dut.cfg_rx_lfc_en.value = 0
dut.cfg_rx_pfc_opcode.value = 0x0101
dut.cfg_rx_pfc_en.value = 1
dut.cfg_quanta_step.value = int(10000*256 / (512*156.25))
dut.cfg_quanta_clk_en.value = 1
tb.log.info("Test release time accuracy")
eth = Ether(src='5A:51:52:53:54:55', dst='01:80:C2:00:00:01', type=0x8808)
test_pkt = eth / struct.pack('!HH8H', 0x0101, 0x0001, 100, 0, 0, 0, 0, 0, 0, 0)
await tb.send_mcf(test_pkt)
while dut.rx_pfc_req.value == 0x00:
await RisingEdge(dut.clk)
dut.rx_pfc_ack.value = 0x01
start_time = get_sim_time('sec')
while dut.rx_pfc_req.value:
await RisingEdge(dut.clk)
stop_time = get_sim_time('sec')
dut.rx_pfc_ack.value = 0x00
pause_time = stop_time-start_time
pause_quanta = pause_time / (512 * 1/10e9)
tb.log.info("pause time : %g s", pause_time)
tb.log.info("pause quanta : %f", pause_quanta)
assert round(pause_quanta) == 100
tb.log.info("Test release time accuracy (with refresh)")
eth = Ether(src='5A:51:52:53:54:55', dst='01:80:C2:00:00:01', type=0x8808)
test_pkt = eth / struct.pack('!HH8H', 0x0101, 0x0001, 100, 0, 0, 0, 0, 0, 0, 0)
await tb.send_mcf(test_pkt)
while dut.rx_pfc_req.value == 0x00:
await RisingEdge(dut.clk)
dut.rx_pfc_ack.value = 0x01
for k in range(400):
await RisingEdge(dut.clk)
await tb.send_mcf(test_pkt)
start_time = get_sim_time('sec')
while dut.rx_pfc_req.value:
await RisingEdge(dut.clk)
stop_time = get_sim_time('sec')
dut.rx_pfc_ack.value = 0x00
pause_time = stop_time-start_time
pause_quanta = pause_time / (512 * 1/10e9)
tb.log.info("pause time : %g s", pause_time)
tb.log.info("pause quanta : %f", pause_quanta)
assert round(pause_quanta) == 100
tb.log.info("Test explicit release")
eth = Ether(src='5A:51:52:53:54:55', dst='01:80:C2:00:00:01', type=0x8808)
test_pkt = eth / struct.pack('!HH8H', 0x0101, 0x0001, 100, 0, 0, 0, 0, 0, 0, 0)
await tb.send_mcf(test_pkt)
while dut.rx_pfc_req.value == 0x00:
await RisingEdge(dut.clk)
dut.rx_pfc_ack.value = 0x01
start_time = get_sim_time('sec')
eth = Ether(src='5A:51:52:53:54:55', dst='01:80:C2:00:00:01', type=0x8808)
test_pkt = eth / struct.pack('!HH8H', 0x0101, 0x0001, 0, 0, 0, 0, 0, 0, 0, 0)
await tb.send_mcf(test_pkt)
while dut.rx_pfc_req.value:
await RisingEdge(dut.clk)
stop_time = get_sim_time('sec')
dut.rx_pfc_ack.value = 0x00
pause_time = stop_time-start_time
pause_quanta = pause_time / (512 * 1/10e9)
tb.log.info("pause time : %g s", pause_time)
tb.log.info("pause quanta : %f", pause_quanta)
assert round(pause_quanta) < 50
tb.log.info("Test all channels")
eth = Ether(src='5A:51:52:53:54:55', dst='01:80:C2:00:00:01', type=0x8808)
test_pkt = eth / struct.pack('!HH8H', 0x0101, 0x00FF, 10, 20, 30, 40, 50, 60, 70, 80)
await tb.send_mcf(test_pkt)
while dut.rx_pfc_req.value != 0xff:
await RisingEdge(dut.clk)
dut.rx_pfc_ack.value = 0xff
start_time = get_sim_time('sec')
for k in range(8):
while dut.rx_pfc_req.value & (1 << k) != 0x00:
await RisingEdge(dut.clk)
stop_time = get_sim_time('sec')
pause_time = stop_time-start_time
pause_quanta = pause_time / (512 * 1/10e9)
tb.log.info("pause time : %g s", pause_time)
tb.log.info("pause quanta : %f", pause_quanta)
assert round(pause_quanta) == (k+1)*10
dut.rx_pfc_ack.value = 0
tb.log.info("Test isolation")
eth = Ether(src='5A:51:52:53:54:55', dst='01:80:C2:00:00:01', type=0x8808)
test_pkt = eth / struct.pack('!HH8H', 0x0101, 0x0001, 100, 0, 0, 0, 0, 0, 0, 0)
await tb.send_mcf(test_pkt)
while dut.rx_pfc_req.value & 0x01 == 0x00:
await RisingEdge(dut.clk)
dut.rx_pfc_ack.value = 0x01
start_time = get_sim_time('sec')
eth = Ether(src='5A:51:52:53:54:55', dst='01:80:C2:00:00:01', type=0x8808)
test_pkt = eth / struct.pack('!HH8H', 0x0101, 0x0002, 0, 200, 0, 0, 0, 0, 0, 0)
await tb.send_mcf(test_pkt)
while dut.rx_pfc_req.value & 0x02 == 0x00:
await RisingEdge(dut.clk)
dut.rx_pfc_ack.value = 0x03
eth = Ether(src='5A:51:52:53:54:55', dst='01:80:C2:00:00:01', type=0x8808)
test_pkt = eth / struct.pack('!HH8H', 0x0101, 0x0002, 0, 0, 0, 0, 0, 0, 0, 0)
await tb.send_mcf(test_pkt)
while dut.rx_pfc_req.value:
await RisingEdge(dut.clk)
stop_time = get_sim_time('sec')
dut.rx_pfc_ack.value = 0x00
pause_time = stop_time-start_time
pause_quanta = pause_time / (512 * 1/10e9)
tb.log.info("pause time : %g s", pause_time)
tb.log.info("pause quanta : %f", pause_quanta)
assert round(pause_quanta) == 100
await RisingEdge(dut.clk)
await RisingEdge(dut.clk)
if cocotb.SIM_NAME:
for test in [run_test_lfc, run_test_pfc]:
factory = TestFactory(test)
factory.generate_tests()
# cocotb-test
tests_dir = os.path.abspath(os.path.dirname(__file__))
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib'))
axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl'))
def test_mac_pause_ctrl_rx(request):
dut = "mac_pause_ctrl_rx"
module = os.path.splitext(os.path.basename(__file__))[0]
toplevel = dut
verilog_sources = [
os.path.join(rtl_dir, f"{dut}.v"),
]
parameters = {}
parameters['MCF_PARAMS_SIZE'] = 18
parameters['PFC_ENABLE'] = 1
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
sim_build = os.path.join(tests_dir, "sim_build",
request.node.name.replace('[', '-').replace(']', ''))
cocotb_test.simulator.run(
python_search=[tests_dir],
verilog_sources=verilog_sources,
toplevel=toplevel,
module=module,
parameters=parameters,
sim_build=sim_build,
extra_env=extra_env,
)

View File

@ -0,0 +1,69 @@
# Copyright (c) 2023 Alex Forencich
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
TOPLEVEL_LANG = verilog
SIM ?= icarus
WAVES ?= 0
COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ps
DUT = mac_pause_ctrl_tx
TOPLEVEL = $(DUT)
MODULE = test_$(DUT)
VERILOG_SOURCES += ../../rtl/$(DUT).v
# module parameters
export PARAM_MCF_PARAMS_SIZE := 18
export PARAM_PFC_ENABLE := 1
ifeq ($(SIM), icarus)
PLUSARGS += -fst
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v:
echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
echo 'end' >> $@
echo 'endmodule' >> $@
clean::
@rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst

View File

@ -0,0 +1,395 @@
#!/usr/bin/env python
"""
Copyright (c) 2023 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import logging
import os
import struct
from scapy.layers.l2 import Ether
import cocotb_test.simulator
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge
from cocotb.regression import TestFactory
from cocotb.utils import get_sim_time
from cocotbext.axi.stream import define_stream
McfBus, McfTransaction, McfSource, McfSink, McfMonitor = define_stream("Mcf",
signals=["valid", "eth_dst", "eth_src", "eth_type", "opcode", "params"],
optional_signals=["ready", "id", "dest", "user"]
)
class TB:
def __init__(self, dut):
self.dut = dut
self.log = logging.getLogger("cocotb.tb")
self.log.setLevel(logging.DEBUG)
cocotb.start_soon(Clock(dut.clk, 6.4, units="ns").start())
self.mcf_sink = McfSink(McfBus.from_prefix(dut, "mcf"), dut.clk, dut.rst)
dut.tx_lfc_req.setimmediatevalue(0)
dut.tx_lfc_resend.setimmediatevalue(0)
dut.tx_pfc_req.setimmediatevalue(0)
dut.tx_pfc_resend.setimmediatevalue(0)
dut.cfg_tx_lfc_eth_dst.setimmediatevalue(0)
dut.cfg_tx_lfc_eth_src.setimmediatevalue(0)
dut.cfg_tx_lfc_eth_type.setimmediatevalue(0)
dut.cfg_tx_lfc_opcode.setimmediatevalue(0)
dut.cfg_tx_lfc_en.setimmediatevalue(0)
dut.cfg_tx_lfc_quanta.setimmediatevalue(0)
dut.cfg_tx_lfc_refresh.setimmediatevalue(0)
dut.cfg_tx_pfc_eth_dst.setimmediatevalue(0)
dut.cfg_tx_pfc_eth_src.setimmediatevalue(0)
dut.cfg_tx_pfc_eth_type.setimmediatevalue(0)
dut.cfg_tx_pfc_opcode.setimmediatevalue(0)
dut.cfg_tx_pfc_en.setimmediatevalue(0)
dut.cfg_tx_pfc_quanta.setimmediatevalue(0)
dut.cfg_tx_pfc_refresh.setimmediatevalue(0)
dut.cfg_quanta_step.setimmediatevalue(256)
dut.cfg_quanta_clk_en.setimmediatevalue(1)
async def reset(self):
self.dut.rst.setimmediatevalue(0)
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
self.dut.rst.value = 1
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
self.dut.rst.value = 0
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
async def recv_mcf(self):
rx_frame = await self.mcf_sink.recv()
data = bytearray()
data.extend(rx_frame.eth_dst.integer.to_bytes(6, 'big'))
data.extend(rx_frame.eth_src.integer.to_bytes(6, 'big'))
data.extend(rx_frame.eth_type.integer.to_bytes(2, 'big'))
data.extend(rx_frame.opcode.integer.to_bytes(2, 'big'))
data.extend(rx_frame.params.integer.to_bytes(44, 'little'))
return Ether(data)
def check_lfc_frame(tb, pkt, quanta):
tb.log.info("Pause frame: %s", repr(pkt))
op, q = struct.unpack_from('!HH', bytes(pkt[Ether].payload), 0)
tb.log.info("opcode: 0x%x", op)
tb.log.info("quanta: %d", q)
assert pkt[Ether].dst == '01:80:c2:00:00:01'
assert pkt[Ether].src == '5a:51:52:53:54:55'
assert pkt[Ether].type == 0x8808
assert op == 0x0001
assert q == quanta
async def run_test_lfc(dut):
tb = TB(dut)
await tb.reset()
dut.tx_lfc_req.value = 0
dut.tx_lfc_resend.value = 0
dut.tx_pfc_req.value = 0x00
dut.tx_pfc_resend.value = 0
dut.cfg_tx_lfc_eth_dst.value = 0x0180C2000001
dut.cfg_tx_lfc_eth_src.value = 0x5A5152535455
dut.cfg_tx_lfc_eth_type.value = 0x8808
dut.cfg_tx_lfc_opcode.value = 0x0001
dut.cfg_tx_lfc_en.value = 1
dut.cfg_tx_lfc_quanta.value = 0xFFFF
dut.cfg_tx_lfc_refresh.value = 0x7F00
dut.cfg_tx_pfc_eth_dst.value = 0x0180C2000001
dut.cfg_tx_pfc_eth_src.value = 0x5A5152535455
dut.cfg_tx_pfc_eth_type.value = 0x8808
dut.cfg_tx_pfc_opcode.value = 0x0101
dut.cfg_tx_pfc_en.value = 0
dut.cfg_tx_pfc_quanta.value = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
dut.cfg_tx_pfc_refresh.value = 0x7F007F007F007F007F007F007F007F00
dut.cfg_quanta_step.value = int(10000*256 / (512*156.25))
dut.cfg_quanta_clk_en.value = 1
tb.log.info("Test pause")
dut.cfg_tx_lfc_refresh.value = 100
dut.tx_lfc_req.value = 1
start_time = None
for k in range(4):
rx_pkt = await tb.recv_mcf()
stop_time = get_sim_time('sec')
check_lfc_frame(tb, rx_pkt, 0xFFFF)
if start_time:
refresh_time = stop_time-start_time
refresh_quanta = refresh_time / (512 * 1/10e9)
tb.log.info("refresh time : %g s", refresh_time)
tb.log.info("refresh quanta : %f", refresh_quanta)
assert round(refresh_quanta) == 100
start_time = get_sim_time('sec')
dut.tx_lfc_req.value = 0
rx_pkt = await tb.recv_mcf()
check_lfc_frame(tb, rx_pkt, 0x0)
await RisingEdge(dut.clk)
await RisingEdge(dut.clk)
def check_pfc_frame(tb, pkt, enable_mask, quanta_mask, quanta):
tb.log.info("PFC frame: %s", repr(pkt))
op, enable, *q = struct.unpack_from('!HH8H', bytes(pkt[Ether].payload), 0)
tb.log.info("opcode: 0x%x", op)
tb.log.info("enable: 0x%x", enable)
tb.log.info("quanta: %r", q)
assert pkt[Ether].dst == '01:80:c2:00:00:01'
assert pkt[Ether].src == '5a:51:52:53:54:55'
assert pkt[Ether].type == 0x8808
assert op == 0x0101
assert enable == enable_mask
for k in range(8):
if quanta_mask & (1 << k):
assert q[k] == quanta
else:
assert q[k] == 0
async def run_test_pfc(dut):
tb = TB(dut)
await tb.reset()
dut.tx_lfc_req.value = 0
dut.tx_lfc_resend.value = 0
dut.tx_pfc_req.value = 0x00
dut.tx_pfc_resend.value = 0
dut.cfg_tx_lfc_eth_dst.value = 0x0180C2000001
dut.cfg_tx_lfc_eth_src.value = 0x5A5152535455
dut.cfg_tx_lfc_eth_type.value = 0x8808
dut.cfg_tx_lfc_opcode.value = 0x0001
dut.cfg_tx_lfc_en.value = 0
dut.cfg_tx_lfc_quanta.value = 0xFFFF
dut.cfg_tx_lfc_refresh.value = 0x7F00
dut.cfg_tx_pfc_eth_dst.value = 0x0180C2000001
dut.cfg_tx_pfc_eth_src.value = 0x5A5152535455
dut.cfg_tx_pfc_eth_type.value = 0x8808
dut.cfg_tx_pfc_opcode.value = 0x0101
dut.cfg_tx_pfc_en.value = 1
dut.cfg_tx_pfc_quanta.value = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
dut.cfg_tx_pfc_refresh.value = 0x7F007F007F007F007F007F007F007F00
dut.cfg_quanta_step.value = int(10000*256 / (512*156.25))
dut.cfg_quanta_clk_en.value = 1
tb.log.info("Test pause")
dut.cfg_tx_pfc_refresh.value = 0x00640064006400640064006400640064
dut.tx_pfc_req.value = 0x01
start_time = None
for k in range(4):
rx_pkt = await tb.recv_mcf()
stop_time = get_sim_time('sec')
check_pfc_frame(tb, rx_pkt, 0x01, 0x01, 0xFFFF)
if start_time:
refresh_time = stop_time-start_time
refresh_quanta = refresh_time / (512 * 1/10e9)
tb.log.info("refresh time : %g s", refresh_time)
tb.log.info("refresh quanta : %f", refresh_quanta)
assert round(refresh_quanta) == 100
start_time = get_sim_time('sec')
dut.tx_pfc_req.value = 0x00
rx_pkt = await tb.recv_mcf()
check_pfc_frame(tb, rx_pkt, 0x01, 0x00, 0xFFFF)
tb.log.info("Test all channels")
dut.cfg_tx_pfc_refresh.value = 0x00640064006400640064006400640064
for ch in range(8):
dut.tx_pfc_req.value = 0xFF >> (7-ch)
start_time = None
for k in range(3):
rx_pkt = await tb.recv_mcf()
stop_time = get_sim_time('sec')
check_pfc_frame(tb, rx_pkt, 0xFF >> (7-ch), 0xFF >> (7-ch), 0xFFFF)
if start_time:
refresh_time = stop_time-start_time
refresh_quanta = refresh_time / (512 * 1/10e9)
tb.log.info("refresh time : %g s", refresh_time)
tb.log.info("refresh quanta : %f", refresh_quanta)
assert round(refresh_quanta) == 100
start_time = get_sim_time('sec')
dut.tx_pfc_req.value = 0x00
rx_pkt = await tb.recv_mcf()
check_pfc_frame(tb, rx_pkt, 0xFF, 0x00, 0xFFFF)
tb.log.info("Test isolation")
dut.cfg_tx_pfc_refresh.value = 0x00640064006400640064006400640064
dut.tx_pfc_req.value = 0x01
start_time = None
rx_pkt = await tb.recv_mcf()
stop_time = get_sim_time('sec')
check_pfc_frame(tb, rx_pkt, 0x01, 0x01, 0xFFFF)
dut.tx_pfc_req.value = 0x03
start_time = None
rx_pkt = await tb.recv_mcf()
stop_time = get_sim_time('sec')
check_pfc_frame(tb, rx_pkt, 0x03, 0x03, 0xFFFF)
dut.tx_pfc_req.value = 0x01
start_time = None
rx_pkt = await tb.recv_mcf()
stop_time = get_sim_time('sec')
check_pfc_frame(tb, rx_pkt, 0x03, 0x01, 0xFFFF)
start_time = get_sim_time('sec')
for k in range(4):
rx_pkt = await tb.recv_mcf()
stop_time = get_sim_time('sec')
check_pfc_frame(tb, rx_pkt, 0x01, 0x01, 0xFFFF)
if start_time:
refresh_time = stop_time-start_time
refresh_quanta = refresh_time / (512 * 1/10e9)
tb.log.info("refresh time : %g s", refresh_time)
tb.log.info("refresh quanta : %f", refresh_quanta)
assert round(refresh_quanta) == 100
start_time = get_sim_time('sec')
dut.tx_pfc_req.value = 0x00
rx_pkt = await tb.recv_mcf()
check_pfc_frame(tb, rx_pkt, 0x01, 0x00, 0xFFFF)
await RisingEdge(dut.clk)
await RisingEdge(dut.clk)
if cocotb.SIM_NAME:
for test in [run_test_lfc, run_test_pfc]:
factory = TestFactory(test)
factory.generate_tests()
# cocotb-test
tests_dir = os.path.abspath(os.path.dirname(__file__))
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib'))
axis_rtl_dir = os.path.abspath(os.path.join(lib_dir, 'axis', 'rtl'))
def test_mac_pause_ctrl_tx(request):
dut = "mac_pause_ctrl_tx"
module = os.path.splitext(os.path.basename(__file__))[0]
toplevel = dut
verilog_sources = [
os.path.join(rtl_dir, f"{dut}.v"),
]
parameters = {}
parameters['MCF_PARAMS_SIZE'] = 18
parameters['PFC_ENABLE'] = 1
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
sim_build = os.path.join(tests_dir, "sim_build",
request.node.name.replace('[', '-').replace(']', ''))
cocotb_test.simulator.run(
python_search=[tests_dir],
verilog_sources=verilog_sources,
toplevel=toplevel,
module=module,
parameters=parameters,
sim_build=sim_build,
extra_env=extra_env,
)