From 739b6aee82236daf64276bd10142d5ab0d59f392 Mon Sep 17 00:00:00 2001 From: azaidy Date: Fri, 19 Jul 2024 14:06:01 -0400 Subject: [PATCH] Add UMI to APB converter --- umi/utils/rtl/umi_apb_dev.v | 336 +++++++++++++++++++ umi/utils/testbench/test_umi_apb_dev.py | 135 ++++++++ umi/utils/testbench/testbench_umi_apb_dev.sv | 205 +++++++++++ 3 files changed, 676 insertions(+) create mode 100644 umi/utils/rtl/umi_apb_dev.v create mode 100755 umi/utils/testbench/test_umi_apb_dev.py create mode 100644 umi/utils/testbench/testbench_umi_apb_dev.sv diff --git a/umi/utils/rtl/umi_apb_dev.v b/umi/utils/rtl/umi_apb_dev.v new file mode 100644 index 0000000..762d95b --- /dev/null +++ b/umi/utils/rtl/umi_apb_dev.v @@ -0,0 +1,336 @@ +/******************************************************************************* + * Copyright 2024 Zero ASIC Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ---- + * + * Documentation: + * + * The module translates a UMI request into a APB requester interface. + * Read data is returned as UMI response packets. Requests can occur + * at a maximum rate of one transaction every two cycles. + * + * This module can also check if the incoming access is within the designated + * address range by setting the GRPOFFSET, GRPAW, and GRPID parameter. + * The address range [GRPOFFSET+:GRPAW] is checked against GRPID for a match. + * To disable the check, set the GRPAW to 0. + * + * Only RW-aligned read/writes <= DW are supported. + * + ******************************************************************************/ + +module umi_apb_dev #( + parameter TARGET = "DEFAULT", // compile target + parameter APB_AW = 64, // APB address width + parameter AW = 64, // UMI address width + parameter CW = 32, // UMI cmd width + parameter DW = 256, // UMI data width + parameter RW = 64, // register width + parameter GRPOFFSET = 24, // group address offset + parameter GRPAW = 0, // group address width + parameter GRPID = 0 // group ID +) +( + input clk, //clk + input nreset, //async active low reset + + // UMI access + input udev_req_valid, + input [CW-1:0] udev_req_cmd, + input [AW-1:0] udev_req_dstaddr, + input [AW-1:0] udev_req_srcaddr, + input [DW-1:0] udev_req_data, + output udev_req_ready, + + output reg udev_resp_valid, + output [CW-1:0] udev_resp_cmd, + output [AW-1:0] udev_resp_dstaddr, + output [AW-1:0] udev_resp_srcaddr, + output [DW-1:0] udev_resp_data, + input udev_resp_ready, + + // Read/Write register interface + output [APB_AW-1:0] paddr, // register address + output [2:0] pprot, // protection type + output psel, // select + output reg penable, // enable + output pwrite, // 0=read, 1=write + output [RW-1:0] pwdata, // write data + output [(RW/8)-1:0] pwstrb, // strobe + input pready, // ready + input [RW-1:0] prdata, // read data + input pslverr // err +); + +`include "umi_messages.vh" + + reg udev_req_valid_r; + reg [CW-1:0] udev_req_cmd_r; + reg [AW-1:0] udev_req_dstaddr_r; + reg [AW-1:0] udev_req_srcaddr_r; + reg [RW-1:0] udev_req_data_r; + + wire incoming_req; + wire outgoing_resp; + wire group_match; + + wire [4:0] req_opcode; + wire [2:0] req_size; + wire [7:0] req_len; + wire [7:0] req_atype; + wire [3:0] req_qos; + wire [1:0] req_prot; + wire req_eom; + wire req_eof; + wire req_ex; + wire [1:0] req_user; + wire [23:0] req_user_extended; + wire [1:0] req_err; + wire [4:0] req_hostid; + + wire cmd_invalid; + wire cmd_request; + wire cmd_response; + wire cmd_read; + wire cmd_write; + wire cmd_write_posted; + wire cmd_rdma; + wire cmd_atomic; + wire cmd_user0; + wire cmd_future0; + wire cmd_error; + wire cmd_link; + wire cmd_read_resp; + wire cmd_write_resp; + wire cmd_user0_resp; + wire cmd_user1_resp; + wire cmd_future0_resp; + wire cmd_future1_resp; + wire cmd_link_resp; + wire cmd_atomic_add; + wire cmd_atomic_and; + wire cmd_atomic_or; + wire cmd_atomic_xor; + wire cmd_atomic_max; + wire cmd_atomic_min; + wire cmd_atomic_maxu; + wire cmd_atomic_minu; + wire cmd_atomic_swap; + + wire [CW-1:0] packet_cmd; + + wire signed_max; + wire unsigned_max; + wire [RW-1:0] atomic_data; + reg atomic_wr; + wire [4:0] cmd_opcode; + reg [1:0] pslverr_r; + reg pvalid_r; + reg [RW-1:0] prdata_r; + + generate + if (GRPAW != 0) + assign group_match = (udev_req_dstaddr[GRPOFFSET+:GRPAW]==GRPID[GRPAW-1:0]); + else + assign group_match = 1'b1; + endgenerate + + assign incoming_req = udev_req_valid & udev_req_ready & group_match; + assign outgoing_resp = (udev_resp_valid & udev_resp_ready) | (pready & cmd_write_posted); + + always @(posedge clk or negedge nreset) begin + if (~nreset) begin + udev_req_cmd_r <= {CW{1'b0}}; + udev_req_dstaddr_r <= {AW{1'b0}}; + udev_req_srcaddr_r <= {AW{1'b0}}; + udev_req_data_r <= {RW{1'b0}}; + end + else if (incoming_req) begin + udev_req_cmd_r <= udev_req_cmd; + udev_req_dstaddr_r <= udev_req_dstaddr; + udev_req_srcaddr_r <= udev_req_srcaddr; + udev_req_data_r <= udev_req_data[RW-1:0]; + end + end + + /* umi_unpack AUTO_TEMPLATE( + .cmd_\(.*\) (req_\1[]), + );*/ + umi_unpack #(.CW(CW)) + umi_unpack(/*AUTOINST*/ + // Outputs + .cmd_opcode (req_opcode[4:0]), // Templated + .cmd_size (req_size[2:0]), // Templated + .cmd_len (req_len[7:0]), // Templated + .cmd_atype (req_atype[7:0]), // Templated + .cmd_qos (req_qos[3:0]), // Templated + .cmd_prot (req_prot[1:0]), // Templated + .cmd_eom (req_eom), // Templated + .cmd_eof (req_eof), // Templated + .cmd_ex (req_ex), // Templated + .cmd_user (req_user[1:0]), // Templated + .cmd_user_extended(req_user_extended[23:0]), // Templated + .cmd_err (req_err[1:0]), // Templated + .cmd_hostid (req_hostid[4:0]), // Templated + // Inputs + .packet_cmd (packet_cmd[CW-1:0])); // Templated + + /* umi_decode AUTO_TEMPLATE( + .command (packet_cmd[]), + );*/ + umi_decode #(.CW(CW)) + umi_decode(/*AUTOINST*/ + // Outputs + .cmd_invalid (cmd_invalid), + .cmd_request (cmd_request), + .cmd_response (cmd_response), + .cmd_read (cmd_read), + .cmd_write (cmd_write), + .cmd_write_posted (cmd_write_posted), + .cmd_rdma (cmd_rdma), + .cmd_atomic (cmd_atomic), + .cmd_user0 (cmd_user0), + .cmd_future0 (cmd_future0), + .cmd_error (cmd_error), + .cmd_link (cmd_link), + .cmd_read_resp (cmd_read_resp), + .cmd_write_resp (cmd_write_resp), + .cmd_user0_resp (cmd_user0_resp), + .cmd_user1_resp (cmd_user1_resp), + .cmd_future0_resp (cmd_future0_resp), + .cmd_future1_resp (cmd_future1_resp), + .cmd_link_resp (cmd_link_resp), + .cmd_atomic_add (cmd_atomic_add), + .cmd_atomic_and (cmd_atomic_and), + .cmd_atomic_or (cmd_atomic_or), + .cmd_atomic_xor (cmd_atomic_xor), + .cmd_atomic_max (cmd_atomic_max), + .cmd_atomic_min (cmd_atomic_min), + .cmd_atomic_maxu (cmd_atomic_maxu), + .cmd_atomic_minu (cmd_atomic_minu), + .cmd_atomic_swap (cmd_atomic_swap), + // Inputs + .command (packet_cmd[CW-1:0])); // Templated + + assign packet_cmd = incoming_req ? udev_req_cmd : udev_req_cmd_r; + + assign paddr = incoming_req ? + udev_req_dstaddr[APB_AW-1:0] : + udev_req_dstaddr_r[APB_AW-1:0]; + assign pprot = {1'b0, req_prot}; + assign pwrite = cmd_write | cmd_write_posted | atomic_wr; + assign pwdata = incoming_req ? udev_req_data[RW-1:0] : + (atomic_wr ? atomic_data[RW-1:0] : udev_req_data_r[RW-1:0]); + assign pwstrb = {(RW/8){1'b1}}; // TODO: Support strobe + + assign psel = udev_req_valid | pvalid_r; + + always @(posedge clk or negedge nreset) + if (~nreset) + penable <= 1'b0; + else if (pready) + penable <= 1'b0; + else if (incoming_req | atomic_wr) + penable <= 1'b1; + + always @(posedge clk or negedge nreset) + if (~nreset) + pvalid_r <= 1'b0; + else if (incoming_req) + pvalid_r <= 1'b1; + else if (pready & (~cmd_atomic | atomic_wr)) + pvalid_r <= 1'b0; + + //######################## + // Handle atomics + //######################## + + assign signed_max = $signed(udev_req_data_r[RW-1:0]) > $signed(udev_resp_data[RW-1:0]); + assign unsigned_max = $unsigned(udev_req_data_r[RW-1:0]) > $unsigned(udev_resp_data[RW-1:0]); + + assign atomic_data[RW-1:0] = cmd_atomic_add ? udev_req_data_r[RW-1:0] + udev_resp_data[RW-1:0] : + cmd_atomic_and ? udev_req_data_r[RW-1:0] & udev_resp_data[RW-1:0] : + cmd_atomic_or ? udev_req_data_r[RW-1:0] | udev_resp_data[RW-1:0] : + cmd_atomic_xor ? udev_req_data_r[RW-1:0] ^ udev_resp_data[RW-1:0] : + (cmd_atomic_max & ~signed_max | + cmd_atomic_maxu & ~unsigned_max | + cmd_atomic_min & signed_max | + cmd_atomic_minu & unsigned_max ) ? udev_resp_data[RW-1:0] : + udev_req_data_r[RW-1:0]; // inlcd. swap + + always @ (posedge clk or negedge nreset) + if(!nreset) + atomic_wr <= 1'b0; + else if (cmd_atomic & pready & ~pwrite) + atomic_wr <= 1'b1; + else if (pready) + atomic_wr <= 1'b0; + + assign udev_req_ready = ~pvalid_r; + + //############################ + //# UMI OUTPUT + //############################ + assign udev_resp_dstaddr[AW-1:0] = udev_req_srcaddr_r[AW-1:0]; + assign udev_resp_srcaddr[AW-1:0] = udev_req_dstaddr_r[AW-1:0]; + assign udev_resp_data[DW-1:0] = {{(DW-RW){1'b0}}, prdata_r[RW-1:0]}; + + always @(posedge clk or negedge nreset) + if (~nreset) + prdata_r <= 'b0; + else if (pready & ~pwrite) + prdata_r <= prdata; + + always @(posedge clk or negedge nreset) + if (~nreset) + udev_resp_valid <= 1'b0; + else if (pready & ~cmd_write_posted & ~atomic_wr) + udev_resp_valid <= 1'b1; + else if (outgoing_resp) + udev_resp_valid <= 1'b0; + + assign cmd_opcode[4:0] = (cmd_read | cmd_atomic) ? UMI_RESP_READ : UMI_RESP_WRITE; + + always @(posedge clk or negedge nreset) + if (~nreset) + pslverr_r <= 'b0; + else if (pready) + pslverr_r <= {pslverr, 1'b0}; + + /*umi_pack AUTO_TEMPLATE( + .packet_cmd (udev_resp_cmd[]), + .cmd_\(.*\) (req_\1[]), + .cmd_opcode (cmd_opcode[]), + .cmd_err (pslverr_r[]), + );*/ + umi_pack #(.CW(CW)) + umi_pack(/*AUTOINST*/ + // Outputs + .packet_cmd (udev_resp_cmd[CW-1:0]), + // Inputs + .cmd_opcode (cmd_opcode[4:0]), // Templated + .cmd_size (req_size[2:0]), // Templated + .cmd_len (req_len[7:0]), // Templated + .cmd_atype (req_atype[7:0]), // Templated + .cmd_prot (req_prot[1:0]), // Templated + .cmd_qos (req_qos[3:0]), // Templated + .cmd_eom (req_eom), // Templated + .cmd_eof (req_eof), // Templated + .cmd_user (req_user[1:0]), // Templated + .cmd_err (pslverr_r[1:0]), // Templated + .cmd_ex (req_ex), // Templated + .cmd_hostid (req_hostid[4:0]), // Templated + .cmd_user_extended (req_user_extended[23:0])); // Templated + +endmodule // umi_apb_dev diff --git a/umi/utils/testbench/test_umi_apb_dev.py b/umi/utils/testbench/test_umi_apb_dev.py new file mode 100755 index 0000000..5d1312f --- /dev/null +++ b/umi/utils/testbench/test_umi_apb_dev.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2023 Zero ASIC +# This code is licensed under Apache License 2.0 (see LICENSE for details) + +import random +import numpy as np +from argparse import ArgumentParser +from switchboard import SbDut, UmiTxRx, delete_queue, verilator_run +import umi + + +def build_testbench(dut): + # Set up inputs + dut.input('utils/testbench/testbench_umi_apb_dev.sv', package='umi') + + dut.use(umi) + dut.add('option', 'library', 'umi') + dut.add('option', 'library', 'lambdalib_ramlib') + + # Verilator configuration + dut.set('tool', 'verilator', 'task', 'compile', 'file', 'config', 'utils/testbench/config.vlt', + package='umi') +# dut.set('option', 'relax', True) + dut.add('tool', 'verilator', 'task', 'compile', 'option', '--prof-cfuncs') + dut.add('tool', 'verilator', 'task', 'compile', 'option', '-CFLAGS') + dut.add('tool', 'verilator', 'task', 'compile', 'option', '-DVL_DEBUG') + dut.add('tool', 'verilator', 'task', 'compile', 'option', '-Wall') + + # Settings - enable tracing + dut.set('tool', 'verilator', 'task', 'compile', 'var', 'trace', True) + dut.set('tool', 'verilator', 'task', 'compile', 'var', 'trace_type', 'fst') + + # Build simulator + dut.run() + + return dut.find_result('vexe', step='compile') + + +def apply_atomic(origdata, atomicdata, operation, maxrange): + tempval = origdata + if (operation == 0): + tempval = origdata + atomicdata + if (tempval >= maxrange): + tempval = tempval - maxrange + elif (operation == 1): + tempval = origdata & atomicdata + elif (operation == 2): + tempval = origdata | atomicdata + elif (operation == 3): + tempval = origdata ^ atomicdata + elif (operation == 4): + if (origdata & (maxrange >> 1)): + origdata = int(origdata) - int(maxrange) + else: + origdata = int(origdata) + if (atomicdata & (maxrange >> 1)): + atomicdata = int(atomicdata) - int(maxrange) + else: + atomicdata = int(atomicdata) + tempval = origdata if (origdata > atomicdata) else atomicdata + elif (operation == 5): + if (origdata & (maxrange >> 1)): + origdata = int(origdata) - int(maxrange) + else: + origdata = int(origdata) + if (atomicdata & (maxrange >> 1)): + atomicdata = int(atomicdata) - int(maxrange) + else: + atomicdata = int(atomicdata) + tempval = atomicdata if (origdata > atomicdata) else origdata + elif (operation == 6): + tempval = origdata if (origdata > atomicdata) else atomicdata + elif (operation == 7): + tempval = atomicdata if (origdata > atomicdata) else origdata + elif (operation == 8): + tempval = atomicdata + else: + tempval = atomicdata + + return tempval + + +def main(host2dut="host2dut_0.q", dut2host="dut2host_0.q"): + + extra_args = { + '--vldmode': dict(type=int, default=2, help='Valid mode'), + '--rdymode': dict(type=int, default=2, help='Ready mode'), + '-n': dict(type=int, default=10, help='Number of transactions' + 'to send during the test.') + } + + dut = SbDut('testbench', cmdline=True, extra_args=extra_args, trace=True, default_main=True) + + verilator_bin = build_testbench(dut) + + # launch the simulation + verilator_run(verilator_bin) + + # instantiate TX and RX queues. note that these can be instantiated without + # specifying a URI, in which case the URI can be specified later via the + # "init" method + + host = UmiTxRx(host2dut, dut2host, fresh=True) + + print("### Starting test ###") + + # regif accesses are all 32b wide and aligned + for _ in range(dut.args.n): + addr = np.random.randint(0, 512) * 4 + # length should not cross the DW boundary - umi_mem_agent limitation + data = np.uint32(random.randrange(2**32-1)) + + print(f"umi writing 0x{data:08x} to addr 0x{addr:08x}") + host.write(addr, data) + atomicopcode = np.random.randint(0, 9) + atomicdata = np.uint32(random.randrange(2**32-1)) + print(f"umi atomic opcode: {atomicopcode} data: {atomicdata:08x} to addr 0x{addr:08x}") + atomicval = host.atomic(addr, atomicdata, atomicopcode) + if not (atomicval == data): + print(f"ERROR umi atomic from addr 0x{addr:08x} expected {data} actual {atomicval}") + assert (atomicval == data) + data = np.array(apply_atomic(data, atomicdata, atomicopcode, 2**32)).astype(np.uint32) + + print(f"umi read from addr 0x{addr:08x}") + val = host.read(addr, np.uint32) + if not (val == data): + print(f"ERROR umi read from addr 0x{addr:08x} expected {data} actual {val}") + assert (val == data) + + print("### TEST PASS ###") + + +if __name__ == '__main__': + main() diff --git a/umi/utils/testbench/testbench_umi_apb_dev.sv b/umi/utils/testbench/testbench_umi_apb_dev.sv new file mode 100644 index 0000000..852bd49 --- /dev/null +++ b/umi/utils/testbench/testbench_umi_apb_dev.sv @@ -0,0 +1,205 @@ +/******************************************************************************* + * Copyright 2020 Zero ASIC Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ---- + * + * Documentation: + * - UMI to APB converter testbench + * + ******************************************************************************/ + +`default_nettype none + +`include "switchboard.vh" + +module testbench ( + input clk +); + + parameter integer APB_AW = 32; + parameter integer AW = 64; + parameter integer CW = 32; + parameter integer DW = 256; + parameter integer RW = 32; + parameter integer CTRLW = 8; + parameter integer RAMDEPTH = 512; + + reg [15:0] nreset_vec; + wire nreset; + + assign nreset = nreset_vec[1]; + + initial begin + nreset_vec = 16'b1; + end + + always @(negedge clk) + nreset_vec <= {nreset_vec[14:0], 1'b1}; + + wire udev_req_valid; + wire [CW-1:0] udev_req_cmd; + wire [AW-1:0] udev_req_dstaddr; + wire [AW-1:0] udev_req_srcaddr; + wire [DW-1:0] udev_req_data; + wire udev_req_ready; + + wire udev_resp_valid; + wire [CW-1:0] udev_resp_cmd; + wire [AW-1:0] udev_resp_dstaddr; + wire [AW-1:0] udev_resp_srcaddr; + wire [DW-1:0] udev_resp_data; + wire udev_resp_ready; + + wire [APB_AW-1:0] paddr; + wire [2:0] pprot; + wire psel; + wire penable; + wire pwrite; + wire [RW-1:0] pwdata; + wire [(RW/8)-1:0] pwstrb; + wire pready; + wire [RW-1:0] prdata; + wire pslverr; + reg randomize_ready; + + umi_apb_dev #( + .APB_AW (APB_AW), + .AW (AW), + .CW (CW), + .DW (DW), + .RW (RW) + ) dut ( + .clk (clk), + .nreset (nreset), + + .udev_req_valid (udev_req_valid), + .udev_req_cmd (udev_req_cmd), + .udev_req_dstaddr (udev_req_dstaddr), + .udev_req_srcaddr (udev_req_srcaddr), + .udev_req_data (udev_req_data), + .udev_req_ready (udev_req_ready), + + .udev_resp_valid (udev_resp_valid), + .udev_resp_cmd (udev_resp_cmd), + .udev_resp_dstaddr (udev_resp_dstaddr), + .udev_resp_srcaddr (udev_resp_srcaddr), + .udev_resp_data (udev_resp_data), + .udev_resp_ready (udev_resp_ready), + + .paddr (paddr), + .pprot (pprot), + .psel (psel), + .penable (penable), + .pwrite (pwrite), + .pwdata (pwdata), + .pwstrb (pwstrb), + .pready (pready), + .prdata (prdata), + .pslverr (pslverr)); + + wire [CTRLW-1:0] sram_ctrl = 8'b0; + + la_spram #( + .DW (RW), + .AW ($clog2(RAMDEPTH)), + .CTRLW (CTRLW), + .TESTW (128) + ) la_spram_i( + // Outputs + .dout (prdata), + // Inputs + .clk (clk), + .ce (psel), + .we (pwrite), + .wmask ({RW{1'b1}}), + .addr (paddr[$clog2(RW) +: $clog2(RAMDEPTH)]), + .din (pwdata), + .vss (1'b0), + .vdd (1'b1), + .vddio (1'b1), + .ctrl (sram_ctrl), + .test (128'h0)); + + assign pready = psel & penable & randomize_ready; + + /* verilator lint_off WIDTHTRUNC */ + always @(posedge clk or negedge nreset) begin + if (~nreset) + randomize_ready <= 1'b0; + else + randomize_ready <= $random%2; + end + /* verilator lint_on WIDTHTRUNC */ + + /////////////////////////////////////////// + // Host side umi agents + /////////////////////////////////////////// + + umi_rx_sim #( + .VALID_MODE_DEFAULT (2), + .DW (DW) + ) host_umi_rx_i ( + .clk (clk), + .valid (udev_req_valid), + .cmd (udev_req_cmd[CW-1:0]), + .dstaddr (udev_req_dstaddr[AW-1:0]), + .srcaddr (udev_req_srcaddr[AW-1:0]), + .data (udev_req_data[DW-1:0]), + .ready (udev_req_ready)); + + umi_tx_sim #( + .READY_MODE_DEFAULT (2), + .DW (DW) + ) host_umi_tx_i ( + .clk (clk), + .valid (udev_resp_valid), + .cmd (udev_resp_cmd[CW-1:0]), + .dstaddr (udev_resp_dstaddr[AW-1:0]), + .srcaddr (udev_resp_srcaddr[AW-1:0]), + .data (udev_resp_data[DW-1:0]), + .ready (udev_resp_ready) + ); + + // Initialize UMI + integer valid_mode, ready_mode; + + initial begin + if (!$value$plusargs("valid_mode=%d", valid_mode)) begin + valid_mode = 2; // default if not provided as a plusarg + end + + if (!$value$plusargs("ready_mode=%d", ready_mode)) begin + ready_mode = 2; // default if not provided as a plusarg + end + + host_umi_rx_i.init("host2dut_0.q"); + host_umi_rx_i.set_valid_mode(valid_mode); + + host_umi_tx_i.init("dut2host_0.q"); + host_umi_tx_i.set_ready_mode(ready_mode); + end + + // control block + `SB_SETUP_PROBES + + // auto-stop + auto_stop_sim auto_stop_sim_i (.clk(clk)); + +endmodule +// Local Variables: +// verilog-library-directories:("../rtl") +// End: + +`default_nettype wire