From a1e53e5e46e4f7f673ba44733e68fb154d2e8f88 Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Sat, 3 Sep 2022 01:20:39 -0700 Subject: [PATCH 1/3] Fix latch inference Signed-off-by: Alex Forencich --- rtl/pcie_us_if_cc.v | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/rtl/pcie_us_if_cc.v b/rtl/pcie_us_if_cc.v index 3810de446..3514655f3 100644 --- a/rtl/pcie_us_if_cc.v +++ b/rtl/pcie_us_if_cc.v @@ -455,6 +455,15 @@ always @* begin end end + eop_index = 0; + for (seg = 0; seg < INT_TLP_SEG_COUNT; seg = seg + 1) begin + for (lane = 0; lane < INT_TLP_SEG_STRB_WIDTH; lane = lane + 1) begin + if (out_tlp_strb[seg*INT_TLP_SEG_STRB_WIDTH+lane]) begin + eop_index[seg*3 +: 3] = lane; + end + end + end + if (!m_axis_cc_tvalid || m_axis_cc_tready) begin // remap header and sideband m_axis_cc_tdata_next = out_tlp_data; @@ -467,13 +476,6 @@ always @* begin if (out_tlp_valid[seg]) begin m_axis_cc_tkeep_next[seg*INT_TLP_SEG_STRB_WIDTH +: INT_TLP_SEG_STRB_WIDTH] = out_tlp_strb[seg*INT_TLP_SEG_STRB_WIDTH +: INT_TLP_SEG_STRB_WIDTH]; end - - eop_index[seg*3 +: 3] = 0; - for (lane = 0; lane < INT_TLP_SEG_STRB_WIDTH; lane = lane + 1) begin - if (out_tlp_strb[seg*INT_TLP_SEG_STRB_WIDTH+lane]) begin - eop_index[seg*3 +: 3] = lane; - end - end end if (AXIS_PCIE_DATA_WIDTH == 512) begin From d038ba985386093f7cdb472663a306d7bee6f006 Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Sat, 3 Sep 2022 17:19:21 -0700 Subject: [PATCH 2/3] Minor cleanup of MSI-X module Signed-off-by: Alex Forencich --- rtl/pcie_msix.v | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/rtl/pcie_msix.v b/rtl/pcie_msix.v index 3ede36ce9..6d418f4f1 100644 --- a/rtl/pcie_msix.v +++ b/rtl/pcie_msix.v @@ -164,9 +164,7 @@ reg [63:0] tbl_axil_mem_wr_data; reg pba_axil_mem_rd_en; reg tbl_mem_rd_en; -reg tbl_mem_wr_en; reg [TBL_ADDR_WIDTH-1:0] tbl_mem_addr; -reg [63:0] tbl_mem_wr_data; reg pba_mem_rd_en; reg pba_mem_wr_en; reg [PBA_ADDR_WIDTH-1:0] pba_mem_addr; @@ -244,9 +242,7 @@ always @* begin state_next = STATE_IDLE; tbl_mem_rd_en = 1'b0; - tbl_mem_wr_en = 1'b0; tbl_mem_addr = {irq_index_reg, 1'b0}; - tbl_mem_wr_data = 0; pba_mem_rd_en = 1'b0; pba_mem_wr_en = 1'b0; @@ -418,14 +414,12 @@ always @(posedge clk) begin if (tbl_mem_rd_en) begin tbl_mem_rd_data_reg <= tbl_mem[tbl_mem_addr]; - end else if (tbl_mem_wr_en) begin - tbl_mem[tbl_mem_addr] <= tbl_mem_wr_data; end - if (pba_mem_rd_en) begin - pba_mem_rd_data_reg <= pba_mem[pba_mem_addr]; - end else if (pba_mem_wr_en) begin + if (pba_mem_wr_en) begin pba_mem[pba_mem_addr] <= pba_mem_wr_data; + end else if (pba_mem_rd_en) begin + pba_mem_rd_data_reg <= pba_mem[pba_mem_addr]; end if (rst) begin From 916faa0bdd16cd6aacb079b2e89ed0c4205fd973 Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Sun, 4 Sep 2022 12:02:26 -0700 Subject: [PATCH 3/3] Add IRQ rate limit module Signed-off-by: Alex Forencich --- rtl/irq_rate_limit.v | 274 +++++++++++++++++++++++ tb/irq_rate_limit/Makefile | 68 ++++++ tb/irq_rate_limit/test_irq_rate_limit.py | 192 ++++++++++++++++ 3 files changed, 534 insertions(+) create mode 100644 rtl/irq_rate_limit.v create mode 100644 tb/irq_rate_limit/Makefile create mode 100644 tb/irq_rate_limit/test_irq_rate_limit.py diff --git a/rtl/irq_rate_limit.v b/rtl/irq_rate_limit.v new file mode 100644 index 000000000..6b0022fa7 --- /dev/null +++ b/rtl/irq_rate_limit.v @@ -0,0 +1,274 @@ +/* + +Copyright (c) 2022 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 + +/* + * IRQ rate limit module + */ +module irq_rate_limit # +( + // Interrupt configuration + parameter IRQ_INDEX_WIDTH = 11 +) +( + input wire clk, + input wire rst, + + /* + * Interrupt request input + */ + input wire [IRQ_INDEX_WIDTH-1:0] in_irq_index, + input wire in_irq_valid, + output wire in_irq_ready, + + /* + * Interrupt request output + */ + output wire [IRQ_INDEX_WIDTH-1:0] out_irq_index, + output wire out_irq_valid, + input wire out_irq_ready, + + /* + * Configuration + */ + input wire [15:0] prescale, + input wire [15:0] min_interval +); + +localparam [1:0] + STATE_INIT = 2'd0, + STATE_IDLE = 2'd1, + STATE_IRQ_IN = 2'd2, + STATE_IRQ_OUT = 2'd3; + +reg [1:0] state_reg = STATE_INIT, state_next; + +reg [IRQ_INDEX_WIDTH-1:0] cur_index_reg = 0, cur_index_next; +reg [IRQ_INDEX_WIDTH-1:0] irq_index_reg = 0, irq_index_next; + +reg mem_rd_en; +reg mem_wr_en; +reg [IRQ_INDEX_WIDTH-1:0] mem_addr; +reg [17+1+1-1:0] mem_wr_data; +reg [17+1+1-1:0] mem_rd_data_reg; +reg mem_rd_data_valid_reg = 1'b0, mem_rd_data_valid_next; + +(* ramstyle = "no_rw_check, mlab" *) +reg [17+1+1-1:0] mem_reg[2**IRQ_INDEX_WIDTH-1:0]; + +reg in_irq_ready_reg = 0, in_irq_ready_next; + +reg [IRQ_INDEX_WIDTH-1:0] out_irq_index_reg = 0, out_irq_index_next; +reg out_irq_valid_reg = 0, out_irq_valid_next; + +assign in_irq_ready = in_irq_ready_reg; + +assign out_irq_index = out_irq_index_reg; +assign out_irq_valid = out_irq_valid_reg; + +integer i; + +initial begin + for (i = 0; i < 2**IRQ_INDEX_WIDTH; i = i + 1) begin + mem_reg[i] = 0; + end +end + +reg [15:0] prescale_count_reg = 0; +reg [16:0] time_count_reg = 0; + +always @(posedge clk) begin + if (prescale_count_reg != 0) begin + prescale_count_reg <= prescale_count_reg - 1; + end else begin + prescale_count_reg <= prescale; + time_count_reg <= time_count_reg + 1; + end + + if (rst) begin + prescale_count_reg <= 0; + time_count_reg <= 0; + end +end + +always @* begin + state_next = STATE_INIT; + + cur_index_next = cur_index_reg; + irq_index_next = irq_index_reg; + + in_irq_ready_next = 1'b0; + + out_irq_index_next = out_irq_index_reg; + out_irq_valid_next = out_irq_valid_reg && !out_irq_ready; + + mem_rd_en = 1'b0; + mem_wr_en = 1'b0; + mem_addr = cur_index_reg; + mem_wr_data = mem_rd_data_reg; + mem_rd_data_valid_next = mem_rd_data_valid_reg; + + case (state_reg) + STATE_INIT: begin + // init - clear all timers + mem_addr = cur_index_reg; + mem_wr_data[0] = 1'b0; + mem_wr_data[1] = 1'b0; + mem_wr_data[2 +: 17] = 0; + mem_wr_en = 1'b1; + cur_index_next = cur_index_reg + 1; + if (cur_index_next != 0) begin + state_next = STATE_INIT; + end else begin + state_next = STATE_IDLE; + end + end + STATE_IDLE: begin + // idle - wait for requests and check timers + in_irq_ready_next = 1'b1; + if (in_irq_valid && in_irq_ready) begin + // new interrupt request + irq_index_next = in_irq_index; + mem_addr = in_irq_index; + mem_rd_en = 1'b1; + mem_rd_data_valid_next = 1'b1; + in_irq_ready_next = 1'b0; + state_next = STATE_IRQ_IN; + end else if (mem_rd_data_valid_reg && mem_rd_data_reg[1] && (mem_rd_data_reg[2 +: 17] - time_count_reg) >> 16 != 0) begin + // timer expired + in_irq_ready_next = 1'b0; + state_next = STATE_IRQ_OUT; + end else begin + // read next timer + irq_index_next = cur_index_reg; + mem_addr = cur_index_reg; + mem_rd_en = 1'b1; + mem_rd_data_valid_next = 1'b1; + cur_index_next = cur_index_reg + 1; + state_next = STATE_IDLE; + end + end + STATE_IRQ_IN: begin + // pass through IRQ + if (mem_rd_data_reg[1]) begin + // timer running, set pending bit + mem_addr = irq_index_reg; + mem_wr_data[0] = 1'b1; + mem_wr_data[1] = 1'b1; + mem_wr_data[2 +: 17] = mem_rd_data_reg[2 +: 17]; + mem_wr_en = 1'b1; + mem_rd_data_valid_next = 1'b0; + + in_irq_ready_next = 1'b1; + state_next = STATE_IDLE; + end else if (!out_irq_valid || out_irq_ready) begin + // timer not running, start timer and generate IRQ + mem_addr = irq_index_reg; + mem_wr_data[0] = 1'b0; + mem_wr_data[1] = min_interval != 0; + mem_wr_data[2 +: 17] = time_count_reg + min_interval; + mem_wr_en = 1'b1; + mem_rd_data_valid_next = 1'b0; + + out_irq_valid_next = 1'b1; + out_irq_index_next = irq_index_reg; + + in_irq_ready_next = 1'b1; + state_next = STATE_IDLE; + end else begin + state_next = STATE_IRQ_IN; + end + end + STATE_IRQ_OUT: begin + // handle timer expiration + if (mem_rd_data_reg[0]) begin + // pending bit set, generate IRQ and restart timer + if (!out_irq_valid || out_irq_ready) begin + mem_addr = irq_index_reg; + mem_wr_data[0] = 1'b0; + mem_wr_data[1] = min_interval != 0; + mem_wr_data[2 +: 17] = time_count_reg + min_interval; + mem_wr_en = 1'b1; + mem_rd_data_valid_next = 1'b0; + + out_irq_valid_next = 1'b1; + out_irq_index_next = irq_index_reg; + + in_irq_ready_next = 1'b1; + state_next = STATE_IDLE; + end else begin + state_next = STATE_IRQ_OUT; + end + end else begin + // pending bit not set, reset timer + mem_addr = irq_index_reg; + mem_wr_data[0] = 1'b0; + mem_wr_data[1] = 1'b0; + mem_wr_data[2 +: 17] = 0; + mem_wr_en = 1'b1; + mem_rd_data_valid_next = 1'b0; + + in_irq_ready_next = 1'b1; + state_next = STATE_IDLE; + end + end + endcase +end + +always @(posedge clk) begin + state_reg <= state_next; + + cur_index_reg <= cur_index_next; + irq_index_reg <= irq_index_next; + + in_irq_ready_reg <= in_irq_ready_next; + + out_irq_index_reg <= out_irq_index_next; + out_irq_valid_reg <= out_irq_valid_next; + + if (mem_wr_en) begin + mem_reg[mem_addr] <= mem_wr_data; + end else if (mem_rd_en) begin + mem_rd_data_reg <= mem_reg[mem_addr]; + end + + mem_rd_data_valid_reg <= mem_rd_data_valid_next; + + if (rst) begin + state_reg <= STATE_INIT; + cur_index_reg <= 0; + in_irq_ready_reg <= 1'b0; + out_irq_valid_reg <= 1'b0; + mem_rd_data_valid_reg <= 1'b0; + end +end + +endmodule + +`resetall diff --git a/tb/irq_rate_limit/Makefile b/tb/irq_rate_limit/Makefile new file mode 100644 index 000000000..1b80fe0d1 --- /dev/null +++ b/tb/irq_rate_limit/Makefile @@ -0,0 +1,68 @@ +# Copyright (c) 2022 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 = irq_rate_limit +TOPLEVEL = $(DUT) +MODULE = test_$(DUT) +VERILOG_SOURCES = ../../rtl/$(DUT).v + +# module parameters +export PARAM_IRQ_INDEX_WIDTH ?= 11 + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).IRQ_INDEX_WIDTH=$(PARAM_IRQ_INDEX_WIDTH) + + 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 += -GIRQ_INDEX_WIDTH=$(PARAM_IRQ_INDEX_WIDTH) + + 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/irq_rate_limit/test_irq_rate_limit.py b/tb/irq_rate_limit/test_irq_rate_limit.py new file mode 100644 index 000000000..ec8e60e9a --- /dev/null +++ b/tb/irq_rate_limit/test_irq_rate_limit.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2022 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 itertools +import logging +import os + +import cocotb_test.simulator + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge, Timer +from cocotb.regression import TestFactory + +from cocotbext.axi.stream import define_stream + + +IrqBus, IrqTransaction, IrqSource, IrqSink, IrqMonitor = define_stream("Irq", + signals=["index", "valid", "ready"] +) + + +class TB(object): + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.start_soon(Clock(dut.clk, 4, units="ns").start()) + + self.irq_source = IrqSource(IrqBus.from_prefix(dut, "in_irq"), dut.clk, dut.rst) + self.irq_sink = IrqSink(IrqBus.from_prefix(dut, "out_irq"), dut.clk, dut.rst) + + dut.prescale.setimmediatevalue(0) + dut.min_interval.setimmediatevalue(0) + + def set_idle_generator(self, generator=None): + if generator: + self.irq_source.set_pause_generator(generator()) + + def set_backpressure_generator(self, generator=None): + if generator: + self.irq_sink.set_pause_generator(generator()) + + async def cycle_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 run_test_irq(dut, idle_inserter=None, backpressure_inserter=None): + + tb = TB(dut) + + await tb.cycle_reset() + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + dut.prescale.setimmediatevalue(249) + dut.min_interval.setimmediatevalue(100) + + tb.log.info("Test interrupts (single shot)") + + for k in range(8): + await tb.irq_source.send(IrqTransaction(index=k)) + + for k in range(8): + irq = await tb.irq_sink.recv() + tb.log.info(irq) + assert irq.index == k + + assert tb.irq_sink.empty() + + await Timer(110, 'us') + + assert tb.irq_sink.empty() + + tb.log.info("Test interrupts (multiple)") + + for n in range(5): + for k in range(8): + await tb.irq_source.send(IrqTransaction(index=k)) + + for k in range(8): + irq = await tb.irq_sink.recv() + tb.log.info(irq) + assert irq.index == k + + assert tb.irq_sink.empty() + + await Timer(99, 'us') + + assert tb.irq_sink.empty() + + await Timer(11, 'us') + + assert not tb.irq_sink.empty() + + for k in range(8): + irq = await tb.irq_sink.recv() + tb.log.info(irq) + assert irq.index == k + + assert tb.irq_sink.empty() + + await Timer(110, 'us') + + assert tb.irq_sink.empty() + + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + +def cycle_pause(): + return itertools.cycle([1, 1, 1, 0]) + + +if cocotb.SIM_NAME: + + for test in [ + run_test_irq + ]: + + factory = TestFactory(test) + factory.add_option("idle_inserter", [None, cycle_pause]) + factory.add_option("backpressure_inserter", [None, cycle_pause]) + factory.generate_tests() + + +# cocotb-test + +tests_dir = os.path.dirname(__file__) +rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl')) + + +def test_irq_rate_limit(request): + dut = "irq_rate_limit" + module = os.path.splitext(os.path.basename(__file__))[0] + toplevel = dut + + verilog_sources = [ + os.path.join(rtl_dir, f"{dut}.v"), + ] + + parameters = {} + + parameters['IRQ_INDEX_WIDTH'] = 11 + + 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, + )