From ba5a883433c0146ccc999743eee21281a8b4cfb7 Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Sun, 23 Jul 2023 16:31:33 -0700 Subject: [PATCH] Add pause/PFC modules Signed-off-by: Alex Forencich --- rtl/mac_pause_ctrl_rx.v | 221 +++++++++ rtl/mac_pause_ctrl_tx.v | 313 +++++++++++++ tb/mac_pause_ctrl_rx/Makefile | 69 +++ .../test_mac_pause_ctrl_rx.py | 424 ++++++++++++++++++ tb/mac_pause_ctrl_tx/Makefile | 69 +++ .../test_mac_pause_ctrl_tx.py | 395 ++++++++++++++++ 6 files changed, 1491 insertions(+) create mode 100644 rtl/mac_pause_ctrl_rx.v create mode 100644 rtl/mac_pause_ctrl_tx.v create mode 100644 tb/mac_pause_ctrl_rx/Makefile create mode 100644 tb/mac_pause_ctrl_rx/test_mac_pause_ctrl_rx.py create mode 100644 tb/mac_pause_ctrl_tx/Makefile create mode 100644 tb/mac_pause_ctrl_tx/test_mac_pause_ctrl_tx.py diff --git a/rtl/mac_pause_ctrl_rx.v b/rtl/mac_pause_ctrl_rx.v new file mode 100644 index 00000000..5bff45f7 --- /dev/null +++ b/rtl/mac_pause_ctrl_rx.v @@ -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 diff --git a/rtl/mac_pause_ctrl_tx.v b/rtl/mac_pause_ctrl_tx.v new file mode 100644 index 00000000..7a56f594 --- /dev/null +++ b/rtl/mac_pause_ctrl_tx.v @@ -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 diff --git a/tb/mac_pause_ctrl_rx/Makefile b/tb/mac_pause_ctrl_rx/Makefile new file mode 100644 index 00000000..656f7433 --- /dev/null +++ b/tb/mac_pause_ctrl_rx/Makefile @@ -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 diff --git a/tb/mac_pause_ctrl_rx/test_mac_pause_ctrl_rx.py b/tb/mac_pause_ctrl_rx/test_mac_pause_ctrl_rx.py new file mode 100644 index 00000000..e19b73a0 --- /dev/null +++ b/tb/mac_pause_ctrl_rx/test_mac_pause_ctrl_rx.py @@ -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, + ) diff --git a/tb/mac_pause_ctrl_tx/Makefile b/tb/mac_pause_ctrl_tx/Makefile new file mode 100644 index 00000000..5daad715 --- /dev/null +++ b/tb/mac_pause_ctrl_tx/Makefile @@ -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 diff --git a/tb/mac_pause_ctrl_tx/test_mac_pause_ctrl_tx.py b/tb/mac_pause_ctrl_tx/test_mac_pause_ctrl_tx.py new file mode 100644 index 00000000..5d3f38f8 --- /dev/null +++ b/tb/mac_pause_ctrl_tx/test_mac_pause_ctrl_tx.py @@ -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, + )