Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a wrapper allowing multiple UMI ports to access a single port memory #132

Merged
merged 3 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions umi/umi/rtl/umi_mem_agent.v
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@
* ----
*
* Documentation:
* - Implements a simple memory array for use in simulation
*
* Known limitation/bugs:
* - Transaction cannot cross the DW boundary
* - Implements a simple memory array
*
******************************************************************************/

Expand Down
156 changes: 156 additions & 0 deletions umi/umi/rtl/umi_ram.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/******************************************************************************
* 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:
* - Implements a simple memory array with multiple UMI access ports
* - The array allows only a single read or write per cycle
*
****************************************************************************/

module umi_ram
#(parameter N = 1, // number of UMI ports
parameter DW = 256, // umi packet width
parameter AW = 64, // address width
parameter CW = 32, // command width
parameter RAMDEPTH = 512,
parameter CTRLW = 8,
parameter SRAMTYPE = "DEFAULT"
)
(// global controls
input clk, // clock signals
input nreset, // async active low reset
input [CTRLW-1:0] sram_ctrl, // Control signal for SRAM
// Device port
input [N-1:0] udev_req_valid,
input [N*CW-1:0] udev_req_cmd,
input [N*AW-1:0] udev_req_dstaddr,
input [N*AW-1:0] udev_req_srcaddr,
input [N*DW-1:0] udev_req_data,
output [N-1:0] udev_req_ready,
output [N-1:0] udev_resp_valid,
output [N*CW-1:0] udev_resp_cmd,
output [N*AW-1:0] udev_resp_dstaddr,
output [N*AW-1:0] udev_resp_srcaddr,
output [N*DW-1:0] udev_resp_data,
input [N-1:0] udev_resp_ready
);

/*AUTOREG*/

/*AUTOWIRE*/
// Beginning of automatic wires (for undeclared instantiated-module outputs)
wire [CW-1:0] mem_resp_cmd;
wire [DW-1:0] mem_resp_data;
wire [AW+N-1:0] mem_resp_dstaddr;
wire [AW+N-1:0] mem_resp_srcaddr;
wire mem_resp_valid;
wire [N-1:0] umi_in_ready;
wire [CW-1:0] umi_out_cmd;
wire [DW-1:0] umi_out_data;
wire [AW-1:0] umi_out_dstaddr;
wire umi_out_ready;
wire [AW-1:0] umi_out_srcaddr;
wire umi_out_valid;
wire [N-1:0] umi_req_grants;
// End of automatics
wire mem_resp_ready;

//##################################################################
//# UMI ENDPOINT (Pipelined Request/Response)
//##################################################################

/*umi_arbiter AUTO_TEMPLATE(
.mode (2'b10),
.mask ({@"vl-width"{1'b0}}),
.requests (udev_req_valid[]),
.grants (umi_req_grants[]),
);*/

umi_arbiter #(.N(N))
umi_arbiter (/*AUTOINST*/
// Outputs
.grants (umi_req_grants[N-1:0]), // Templated
// Inputs
.clk (clk),
.nreset (nreset),
.mode (2'b10), // Templated
.mask ({N{1'b0}}), // Templated
.requests (udev_req_valid[N-1:0])); // Templated

assign udev_req_ready[N-1:0] = umi_in_ready[N-1:0] & umi_req_grants[N-1:0];

/*umi_mux AUTO_TEMPLATE(
.umi_in_valid (umi_req_grants[]),
.umi_in_ready (umi_in_ready[]),
.umi_in_\(.*\) (udev_req_\1[]),
);*/

umi_mux #(.CW(CW),
.AW(AW),
.DW(DW),
.N(N))
umi_mux(/*AUTOINST*/
// Outputs
.umi_in_ready (umi_in_ready[N-1:0]), // Templated
.umi_out_valid (umi_out_valid),
.umi_out_cmd (umi_out_cmd[CW-1:0]),
.umi_out_dstaddr (umi_out_dstaddr[AW-1:0]),
.umi_out_srcaddr (umi_out_srcaddr[AW-1:0]),
.umi_out_data (umi_out_data[DW-1:0]),
// Inputs
.umi_in_valid (umi_req_grants[N-1:0]), // Templated
.umi_in_cmd (udev_req_cmd[N*CW-1:0]), // Templated
.umi_in_dstaddr (udev_req_dstaddr[N*AW-1:0]), // Templated
.umi_in_srcaddr (udev_req_srcaddr[N*AW-1:0]), // Templated
.umi_in_data (udev_req_data[N*DW-1:0]), // Templated
.umi_out_ready (umi_out_ready));

assign udev_resp_valid[N-1:0] = mem_resp_srcaddr[AW+:N] & {N{mem_resp_valid}};
assign mem_resp_ready = |(mem_resp_srcaddr[AW+:N] & udev_resp_ready[N-1:0]) | ~mem_resp_valid;
assign udev_resp_cmd[N*CW-1:0] = {N{mem_resp_cmd[CW-1:0]}};
assign udev_resp_dstaddr[N*AW-1:0] = {N{mem_resp_dstaddr[AW-1:0]}};
assign udev_resp_srcaddr[N*AW-1:0] = {N{mem_resp_srcaddr[AW-1:0]}};
assign udev_resp_data[N*DW-1:0] = {N{mem_resp_data[DW-1:0]}};

umi_mem_agent #(.CW(CW),
.AW(AW+N),
.DW(DW),
.RAMDEPTH(RAMDEPTH),
.CTRLW(CTRLW),
.SRAMTYPE(SRAMTYPE))
umi_mem_agent(.clk (clk),
.nreset (nreset),
.sram_ctrl (sram_ctrl),

.udev_req_valid (umi_out_valid),
.udev_req_cmd (umi_out_cmd[CW-1:0]),
.udev_req_dstaddr ({umi_req_grants[N-1:0], umi_out_dstaddr[AW-1:0]}),
.udev_req_srcaddr ({{N{1'b0}}, umi_out_srcaddr[AW-1:0]}),
.udev_req_data (umi_out_data[DW-1:0]),
.udev_req_ready (umi_out_ready),

.udev_resp_valid (mem_resp_valid),
.udev_resp_cmd (mem_resp_cmd[CW-1:0]),
.udev_resp_dstaddr (mem_resp_dstaddr[AW+N-1:0]),
.udev_resp_srcaddr (mem_resp_srcaddr[AW+N-1:0]),
.udev_resp_data (mem_resp_data[DW-1:0]),
.udev_resp_ready (mem_resp_ready));

endmodule
// Local Variables:
// verilog-library-directories:("./")
// End:
147 changes: 147 additions & 0 deletions umi/umi/testbench/test_umi_ram.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env python3

# Copyright (C) 2023 Zero ASIC
# This code is licensed under Apache License 2.0 (see LICENSE for details)

import numpy as np
from argparse import ArgumentParser
from switchboard import SbDut, UmiTxRx, verilator_run
import umi


def build_testbench():
dut = SbDut('testbench', default_main=True)

# Set up inputs
dut.input('umi/testbench/testbench_umi_ram.sv', package='umi')

dut.use(umi)
dut.add('option', 'library', 'umi')
dut.add('option', 'library', 'lambdalib_stdlib')
dut.add('option', 'library', 'lambdalib_ramlib')
dut.add('option', 'library', 'lambdalib_vectorlib')

# Verilator configuration
dut.set('tool', 'verilator', 'task', 'compile', 'file', 'config', 'umi/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('option', '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(vldmode="2", rdymode="2", n=100):

verilator_bin = build_testbench()

# launch the simulation
verilator_run(verilator_bin, plusargs=['trace', ('valid_mode', vldmode), ('ready_mode', rdymode)])

# 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(f'host2dut_{x}.q', f'dut2host_{x}.q', fresh=True) for x in range(5)]

print("### Statring test ###")

avail_datatype = [np.uint8, np.uint16, np.uint32]

# un-aligned accesses
for _ in range(n):
psel = np.random.choice([0, 1, 2, 3, 4])
addr = np.random.randint(0, 512)
# length should not cross the DW boundary - umi_mem_agent limitation
length = np.random.randint(0, 256)
wordindexer = np.random.choice([0, 1, 2])
maxrange = 2**(8*(2**wordindexer))
data = np.random.randint(0, maxrange, size=(length+1), dtype=avail_datatype[wordindexer])
addr = addr*(2**wordindexer) & 0x1FF

print(f"umi{psel} writing {length+1} words of type {avail_datatype[wordindexer]} to addr 0x{addr:08x}")
host[psel].write(addr, data)

psel = np.random.choice([0, 1, 2, 3, 4])
atomicopcode = np.random.randint(0, 9)
atomicdata = np.random.randint(0, 256, dtype=avail_datatype[wordindexer])
print(f"umi{psel} atomic opcode: {atomicopcode} of type {avail_datatype[wordindexer]} to addr 0x{addr:08x}")
atomicval = host[psel].atomic(addr, atomicdata, atomicopcode)
if not (atomicval == data[0]):
print(f"ERROR umi atomic from addr 0x{addr:08x} expected {data[0]} actual {atomicval}")
assert (atomicval == data[0])
data[0] = apply_atomic(data[0], atomicdata, atomicopcode, maxrange)

psel = np.random.choice([0, 1, 2, 3, 4])
print(f"umi{psel} read from addr 0x{addr:08x}")
val = host[psel].read(addr, length+1, dtype=avail_datatype[wordindexer])
if not (np.array_equal(val, data)):
print(f"ERROR umi read from addr 0x{addr:08x} expected {data} actual {val}")
assert (np.array_equal(val, data))

print("### TEST PASS ###")


if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument('--vldmode', default='2')
parser.add_argument('--rdymode', default='2')
parser.add_argument('-n', type=int, default=10,
help='Number of transactions to send during the test.')
args = parser.parse_args()

main(vldmode=args.vldmode,
rdymode=args.rdymode,
n=args.n)
Loading