diff options
Diffstat (limited to '')
| -rw-r--r-- | rtl/axi_timer/axi_timer.sv | 125 | ||||
| -rw-r--r-- | rtl/axi_timer/axi_timer_top.sv | 45 | ||||
| -rw-r--r-- | rtl/axi_timer/mod.mk | 10 | ||||
| -rw-r--r-- | rtl/axi_timer/testbench.py | 0 | ||||
| -rw-r--r-- | rtl/mod.mk | 2 | ||||
| -rw-r--r-- | rtl/pkt_switch/axi_bus.sv (renamed from rtl/axi_timer/axi_bus.sv) | 0 | ||||
| -rw-r--r-- | rtl/pkt_switch/mod.mk | 9 | ||||
| -rw-r--r-- | rtl/pkt_switch/pkt_switch.v | 136 | ||||
| -rw-r--r-- | rtl/pkt_switch/testbench.py | 283 |
9 files changed, 429 insertions, 181 deletions
diff --git a/rtl/axi_timer/axi_timer.sv b/rtl/axi_timer/axi_timer.sv deleted file mode 100644 index 80a1805..0000000 --- a/rtl/axi_timer/axi_timer.sv +++ /dev/null @@ -1,125 +0,0 @@ -//axi_timer top file - -module axi_timer( - input i_clk, - input i_rst_n, - - axi_bus.Slave axi_slave, - - output logic o_IRQ - ); - -// verilator lint_off CASEINCOMPLETE -// verilator lint_off WIDTHEXPAND -// verilator lint_off WIDTHTRUNC - -enum logic [2:0] { - CR_ADDR = 3'd0, - SR_ADDR = 3'd1, - PERIOD_ADDR = 3'd2, - COUNTER_ADDR = 3'd3, - IRQ_CNT_ADDR = 3'd4 -} address_map; - -/*Register map for axi_timer */ -//CR register -logic enable; -//SR register -logic irq; -//PERIOD register -logic [31:0] period; -//COUNTER register -logic [31:0] counter; -//IRQ_CNT register -logic [31:0] irq_cnt; - -/* Native interface for access to register */ -logic addr_valid; -logic addr; -logic addr_ready; -logic addr_write; - -logic write_valid; -logic [31:0] wdata; -logic write_ready; - -logic read_valid; -logic [31:0] rdata; -logic read_ready; - -/* Address section */ -assign addr_valid = axi_slave.AVALID; -assign addr_write = axi_slave.AWRITE; -assign axi_slave.AREADY = addr_ready; - -always_ff @ (posedge i_clk, negedge i_rst_n) - if(!i_rst_n) - addr_ready <= 1'b0; - else if(addr_valid & addr_write) - addr_ready <= 1'b1; - else - addr_ready <= 1'b0; - -always_ff @ (posedge i_clk, negedge i_rst_n) - if(!i_rst_n) - addr <= 'h0; - else if(addr_ready & addr_write) - addr <= axi_slave.ADDR; - -/*Write section */ -assign write_valid = axi_slave.WVALID; -assign axi_slave.WREADY = write_ready; -assign wdata = axi_slave.WDATA; - -always_ff @ (posedge i_clk, negedge i_rst_n) - if(!i_rst_n) - write_ready <= 1'b0; - else if(write_valid) - write_ready <= 1'b1; - else - write_ready <= 1'b0; - -/* registers write logic */ -always_ff @ (posedge i_clk, negedge i_rst_n) - if(!i_rst_n) begin - enable <= 1'b0; - period <= 32'd0; - irq_cnt <= 32'd0; - end - else if(write_valid) begin - case(addr) - CR_ADDR: enable <= wdata[0]; - PERIOD_ADDR: counter <= wdata; - IRQ_CNT_ADDR: irq_cnt <= wdata; - endcase - end - -/*Read section */ -assign axi_slave.RVALID = read_valid; -assign read_ready = axi_slave.RREADY; -assign axi_slave.RDATA = rdata; - -always_ff @ (posedge i_clk, negedge i_rst_n) - if(!i_rst_n) begin - rdata <= 32'd0; - read_valid <= 1'b0; - end - else if(read_ready) begin - read_valid <= 1'b1; - case(addr) - CR_ADDR: rdata <= {31'd0, enable}; - SR_ADDR: rdata <= {31'd0, irq}; - PERIOD_ADDR: rdata <= period; - COUNTER_ADDR: rdata <= counter; - IRQ_CNT_ADDR: rdata <= irq_cnt; - endcase - end - else begin - read_valid <= 1'b0; - rdata <= 32'd0; - end - - - - -endmodule //axi_timer diff --git a/rtl/axi_timer/axi_timer_top.sv b/rtl/axi_timer/axi_timer_top.sv deleted file mode 100644 index 6bd0e2a..0000000 --- a/rtl/axi_timer/axi_timer_top.sv +++ /dev/null @@ -1,45 +0,0 @@ -module axi_timer_top -( - input logic clk, - rst_n, - - input logic[31:0] addr, - input logic avalid, - input logic awrite, - output logic aready, - - input logic wvalid, - input logic[31:0] wdata, - output logic wready, - - input logic rready, - output logic[31:0] rdata, - output logic rvalid, - - output logic irq -); - - axi_bus axi(); - - assign axi.Master.ADDR = addr; - assign axi.Master.AVALID = avalid; - assign axi.Master.AWRITE = awrite; - assign aready = axi.Master.AREADY; - - assign axi.Master.WVALID = wvalid; - assign axi.Master.WDATA = wdata; - assign wready = axi.Master.WREADY; - - assign axi.Master.RREADY = rready; - assign rdata = axi.Master.RDATA; - assign rvalid = axi.Master.RVALID; - - axi_timer timer - ( - .i_clk(clk), - .i_rst_n(rst_n), - .o_IRQ(irq), - .axi_slave(axi.Slave) - ); - -endmodule diff --git a/rtl/axi_timer/mod.mk b/rtl/axi_timer/mod.mk deleted file mode 100644 index 2efe0f4..0000000 --- a/rtl/axi_timer/mod.mk +++ /dev/null @@ -1,10 +0,0 @@ -define core - $(this)/targets := sim test - - $(this)/rtl_top := axi_timer_top - $(this)/rtl_dirs := . - $(this)/rtl_files := axi_bus.sv axi_timer_top.sv - - $(this)/cocotb_paths := . - $(this)/cocotb_modules := testbench -endef diff --git a/rtl/axi_timer/testbench.py b/rtl/axi_timer/testbench.py deleted file mode 100644 index e69de29..0000000 --- a/rtl/axi_timer/testbench.py +++ /dev/null @@ -1,5 +1,5 @@ cores := config debounce intc -subdirs := axi_timer cache core dma_axi32 fpu gfx perf picorv32 smp top wb2axip +subdirs := cache core dma_axi32 fpu gfx perf picorv32 pkt_switch smp top wb2axip define core/config $(this)/rtl_include_dirs := . diff --git a/rtl/axi_timer/axi_bus.sv b/rtl/pkt_switch/axi_bus.sv index f1460ca..f1460ca 100644 --- a/rtl/axi_timer/axi_bus.sv +++ b/rtl/pkt_switch/axi_bus.sv diff --git a/rtl/pkt_switch/mod.mk b/rtl/pkt_switch/mod.mk new file mode 100644 index 0000000..a76ebf0 --- /dev/null +++ b/rtl/pkt_switch/mod.mk @@ -0,0 +1,9 @@ +define core + $(this)/targets := test + + $(this)/rtl_top := pkt_switch + $(this)/rtl_files := pkt_switch.v + + $(this)/cocotb_paths := . + $(this)/cocotb_modules := testbench +endef diff --git a/rtl/pkt_switch/pkt_switch.v b/rtl/pkt_switch/pkt_switch.v new file mode 100644 index 0000000..c146b5e --- /dev/null +++ b/rtl/pkt_switch/pkt_switch.v @@ -0,0 +1,136 @@ + +//Simple pkt switch, functionality: +// - 1 data input interface, 2 data output interfaces +// - data width is 8 bits, with "valid" bit +// - packet is tranmitted continuously (valid = 1, cannot fall in the middle) +// - when valid = 0 that means a gap between packets, packet always starts when valid 0->1 +// - first byte of the packet is address, second is packet length (in bytes), following by the data +// - non-filtered packets are transmitted on interface 0, filtered on 1 +// - if filtering is not enabled, packets are simply forwarded on interface 0 +// - 1 control interface (write only) +// - registers: +// - ADDR: FUNCTIONALITY: +// - 000 settings: +// bit 0 - enable address-based filtering +// bit 1 - enable length-based filtering +// bit 2 - transmit packet on both interfaces +// - 010 address for address-based filtering +// - 011 address based filtering mask (which bits of the address are valid) +// - 100 lower size limit for length based filtering +// - 101 uppoer size limit for length based filtering + +module pkt_switch ( + clk, + rst_n, + datain_data, + datain_valid, + dataout0_data, + dataout1_data, + dataout0_valid, + dataout1_valid, + ctrl_addr, + ctrl_data, + ctrl_wr +); + + input clk, rst_n; + //data interfaces + input [7:0] datain_data; + input datain_valid; + output reg [7:0] dataout0_data, dataout1_data; + output reg dataout0_valid, dataout1_valid; + //control interface + input [2:0] ctrl_addr; + input [7:0] ctrl_data; + input ctrl_wr; + + //config registers + reg addr_filtering_ena_r; + reg len_filtering_ena_r; + reg transmit_both_ena_r; + + reg [7:0] filter_addr_r; + reg [7:0] filter_addr_mask_r; + reg [7:0] lower_size_limit_r; + reg [7:0] upper_size_limit_r; + + reg [7:0] data_0_r, data_1_r; + reg datain_valid_0_r, datain_valid_1_r; + reg [7:0] pkt_addr_r, pkt_len_r; + + wire addr_filtering_active, channel0_active, channel1_active, len_filtering_active; + + always @(posedge clk or negedge rst_n) + begin : config_proc + if(~rst_n) begin + addr_filtering_ena_r <= 1'b0; + len_filtering_ena_r <= 1'b0; + transmit_both_ena_r <= 1'b0; + filter_addr_r <= 8'd0; + filter_addr_mask_r <= 8'd0; + lower_size_limit_r <= 8'd0; + upper_size_limit_r <= 8'd0; + end else if (ctrl_wr) begin + case (ctrl_addr) + 3'b000: begin + addr_filtering_ena_r <= ctrl_data[0]; + len_filtering_ena_r <= ctrl_data[1]; + transmit_both_ena_r <= ctrl_data[2]; + end + 3'b010: filter_addr_r <= ctrl_data; + 3'b011: filter_addr_mask_r <= ctrl_data; + 3'b100: lower_size_limit_r <= ctrl_data; + 3'b101: upper_size_limit_r <= ctrl_data; + default: ; + endcase + end + end + + always @(posedge clk or negedge rst_n) + begin : data_proc + if(~rst_n) begin + data_0_r <= 8'd0; + data_1_r <= 8'd0; + dataout0_data <= 8'd0; + dataout1_data <= 8'd0; + datain_valid_0_r <= 1'b0; + datain_valid_1_r <= 1'b0; + end else begin + data_0_r <= datain_data; + data_1_r <= data_0_r; + datain_valid_0_r <= datain_valid; + datain_valid_1_r <= datain_valid_0_r; + dataout0_data <= (channel0_active) ? data_1_r : 8'd0; + dataout0_valid <= (channel0_active) ? datain_valid_1_r : 1'b0; + dataout1_data <= (channel1_active) ? data_1_r : 8'd0; + dataout1_valid <= (channel1_active) ? datain_valid_1_r : 1'b0; + end + end + + always @(posedge clk or negedge rst_n) + begin : header_proc + if(~rst_n) begin + pkt_addr_r <= 8'd0; + pkt_len_r <= 8'd0; + end else begin + if (datain_valid && !datain_valid_0_r) //first packet byte + pkt_addr_r <= datain_data; + if (datain_valid_0_r && !datain_valid_1_r) //second packet byte + pkt_len_r <= datain_data; + end + end + + assign addr_filtering_active = + addr_filtering_ena_r && + ((pkt_addr_r & filter_addr_mask_r) == (filter_addr_r & filter_addr_mask_r)); + assign len_filtering_active = + len_filtering_ena_r && + ((pkt_len_r >= lower_size_limit_r) && (pkt_len_r <= upper_size_limit_r)); + + assign channel0_active = + !addr_filtering_active && !len_filtering_active; + assign channel1_active = + transmit_both_ena_r || + addr_filtering_active || len_filtering_active; + +endmodule diff --git a/rtl/pkt_switch/testbench.py b/rtl/pkt_switch/testbench.py new file mode 100644 index 0000000..8a8b22a --- /dev/null +++ b/rtl/pkt_switch/testbench.py @@ -0,0 +1,283 @@ + +'''Copyright (c) 2020-2023, MC ASIC Design Consulting +All rights reserved. + +Author: Marek Cieplucha, https://github.com/mciepluc + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met (The BSD 2-Clause +License): + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL POTENTIAL VENTURES LTD BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' + +""" +Example packet switch testbench with functional coverage and constrained +randomization. Simple packet switch is a module that routes packets from the +input interface to output interfaces (1 or 2) depending on configured address +or length based filter. Test generates random packets and checks if it has been +transmitted correctly. +""" + +import cocotb +from cocotb.triggers import Timer, RisingEdge, ReadOnly + +from cocotb_bus.drivers import BusDriver +from cocotb_bus.monitors import BusMonitor + +from cocotb_coverage.coverage import * +from cocotb_coverage.crv import * + +import numpy as np + +class Packet(Randomized): + def __init__(self, data = [0, 3, 0]): + Randomized.__init__(self) + self.addr = data[0] + self.len = len(data) + self.payload = data[2:] + + self.add_rand("addr", list(range(256))) + self.add_rand("len", list(range(3,32))) + + def post_randomize(self): + self.payload = [np.random.randint(256) for _ in range(self.len-2)] + +class PacketIFDriver(BusDriver): + ''' + Packet Interface Driver + ''' + _signals = ["data", "valid"] + + def __init__(self, entity, name, clock): + BusDriver.__init__(self, entity, name, clock) + self.clock = clock + self.bus.data.setimmediatevalue(0) + self.bus.valid.setimmediatevalue(0) + + @cocotb.coroutine + def send(self, packet): + self.bus.valid.value = 1 + # transmit header + self.bus.data.value = packet.addr + yield RisingEdge(self.clock) + self.bus.data.value = packet.len + yield RisingEdge(self.clock) + for byte in packet.payload: + self.bus.data.value = byte + yield RisingEdge(self.clock) + self.bus.valid.value = 0 + yield RisingEdge(self.clock) + +class PacketIFMonitor(BusMonitor): + ''' + Packet Interface Monitor + ''' + _signals = ["data", "valid"] + + def __init__(self, entity, name, clock): + BusMonitor.__init__(self, entity, name, clock) + self.clock = clock + + @cocotb.coroutine + def _monitor_recv(self): + pkt_receiving = False + received_data = [] + while True: + yield RisingEdge(self.clock) + yield ReadOnly() + if (self.bus.valid == 1): + pkt_receiving = True + received_data.append(int(self.bus.data)) + elif pkt_receiving and (self.bus.valid == 0): # packet ended + pkt = Packet(received_data) + self._recv(pkt) + pkt_receiving = False + received_data = [] + +# simple clock generator +@cocotb.coroutine +def clock_gen(signal, period=10000): + while True: + signal.value = 0 + yield Timer(period/2) + signal.value = 1 + yield Timer(period/2) + +@cocotb.test() +async def pkt_switch_test(dut): + """ PKT_SWITCH Test """ + + log = cocotb.logging.getLogger("cocotb.test") # logger instance + await cocotb.start(clock_gen(dut.clk, period=100)) # start clock running + + # reset & init + dut.rst_n.value = 1 + dut.datain_data.value = 0 + dut.datain_valid.value = 0 + dut.ctrl_addr.value = 0 + dut.ctrl_data.value = 0 + dut.ctrl_wr.value = 0 + + await Timer(1000) + dut.rst_n.value = 0 + await Timer(1000) + dut.rst_n.value = 1 + + # procedure of writing configuration registers + @cocotb.coroutine + def write_config(addr, data): + for [a, d] in zip(addr, data): + dut.ctrl_addr.value = a + dut.ctrl_data.value = d + dut.ctrl_wr.value = 1 + yield RisingEdge(dut.clk) + dut.ctrl_wr.value = 0 + + enable_transmit_both = lambda: write_config([0], [4]) + disable_filtering = lambda: write_config([0], [0]) + + @cocotb.coroutine + def enable_addr_filtering(addr, mask): + yield write_config([0, 2, 3], [1, addr, mask]) + + @cocotb.coroutine + def enable_len_filtering(low_limit, up_limit): + yield write_config([0, 4, 5], [2, low_limit, up_limit]) + + driver = PacketIFDriver(dut, name="datain", clock=dut.clk) + monitor0 = PacketIFMonitor(dut, name="dataout0", clock=dut.clk) + monitor1 = PacketIFMonitor(dut, name="dataout1", clock=dut.clk) + + expected_data0 = [] # queue of expeced packet at interface 0 + expected_data1 = [] # queue of expeced packet at interface 1 + + + def scoreboarding(pkt, queue_expected): + assert pkt.addr == queue_expected[0].addr + assert pkt.len == queue_expected[0].len + assert pkt.payload == queue_expected[0].payload + queue_expected.pop() + + monitor0.add_callback(lambda _: scoreboarding(_, expected_data0)) + monitor1.add_callback(lambda _: scoreboarding(_, expected_data1)) + monitor0.add_callback(lambda _: log.info("Receiving packet on interface 0 (packet not filtered)")) + monitor1.add_callback(lambda _: log.info("Receiving packet on interface 1 (packet filtered)")) + + # functional coverage - check received packet + + @CoverPoint( + "top.packet_length", + xf = lambda pkt, event, addr, mask, ll, ul: pkt.len, # packet length + bins = list(range(3,32)) # may be 3 ... 31 bytes + ) + @CoverPoint("top.event", vname="event", bins = ["DIS", "TB", "AF", "LF"]) + @CoverPoint( + "top.filt_addr", + xf = lambda pkt, event, addr, mask, ll, ul: # filtering based on particular bits in header + (addr & mask & 0x0F) if event == "AF" else None, # check only if event is "address filtering" + bins = list(range(16)), # check only 4 LSBs if all options tested + ) + @CoverPoint( + "top.filt_len_eq", + xf = lambda pkt, event, addr, mask, ll, ul: ll == ul, # filtering of a single packet length + bins = [True, False] + ) + @CoverPoint( + "top.filt_len_ll", + vname = "ll", # lower limit of packet length + bins = list(range(3,32)) # 3 ... 31 + ) + @CoverPoint( + "top.filt_len_ul", + vname = "ul", # upper limit of packet length + bins = list(range(3,32)) # 3 ... 31 + ) + @CoverCross( + "top.filt_len_ll_x_packet_length", + items = ["top.packet_length", "top.filt_len_ll"] + ) + @CoverCross( + "top.filt_len_ul_x_packet_length", + items = ["top.packet_length", "top.filt_len_ul"] + ) + def log_sequence(pkt, event, addr, mask, ll, ul): + log.info("Processing packet:") + log.info(" ADDRESS: %X", pkt.addr) + log.info(" LENGTH: %d", pkt.len) + log.info(" PAYLOAD: " + str(pkt.payload)) + if event == "DIS": + log.info("Filtering disabled") + elif event == "TB": + log.info("Transmit on both interfaces") + elif event == "AF": + log.info("Address filtering, address: %02X, mask: %02X", addr, mask) + elif event == "LF": + log.info("Length filtering, lower limit: %d, upper limit: %d", ll, ul) + + # main loop + for _ in range(1000): # is that enough repetitions to ensure coverage goal? Check out! + + event = np.random.choice(["DIS", "TB", "AF", "LF"]) + # DIS - disable filtering : expect all packets on interface 0 + # TB - transmit bot : expect all packets on interface 0 and 1 + # AF - address filtering : expect filtered packets on interface 1, others on 0 + # LF - length filtering : expect filtered packets on interface 1, others on 0 + + # randomize test data + pkt = Packet(); + pkt.randomize() + addr = np.random.randint(256) # 0x00 .. 0xFF + mask = np.random.randint(256) # 0x00 .. 0xFF + low_limit = np.random.randint(3,32) # 3 ... 31 + up_limit = np.random.randint(low_limit,32) # low_limit ... 31 + + # expect the packet on the particular interface + if event == "DIS": + await disable_filtering() + expected_data0.append(pkt) + elif event == "TB": + await enable_transmit_both() + expected_data0.append(pkt) + expected_data1.append(pkt) + elif event == "AF": + await enable_addr_filtering(addr, mask) + if ((pkt.addr & mask) == (addr & mask)): + expected_data1.append(pkt) + else: + expected_data0.append(pkt) + elif event == "LF": + await enable_len_filtering(low_limit, up_limit) + if (low_limit <= pkt.len <= up_limit): + expected_data1.append(pkt) + else: + expected_data0.append(pkt) + + # wait DUT + await driver.send(pkt) + await RisingEdge(dut.clk) + await RisingEdge(dut.clk) + + # LOG the action + log_sequence(pkt, event, addr, mask, low_limit, up_limit) + + # print coverage report + coverage_db.report_coverage(log.info, bins=False) + # export + coverage_db.export_to_xml(filename="coverage_pkt_switch.xml") + coverage_db.export_to_yaml(filename="coverage_pkt_switch.yml") |
