Skip to content

Commit

Permalink
modules/zstd/memory/AxiWriter: Add cocotb test
Browse files Browse the repository at this point in the history
Co-authred-by: Pawel Czarnecki <[email protected]>
Co-authred-by: Robert Winkler <[email protected]>
Signed-off-by: Michal Czyz <[email protected]>
Signed-off-by: Pawel Czarnecki <[email protected]>
Signed-off-by: Robert Winkler <[email protected]>
  • Loading branch information
mczyz-antmicro authored and lpawelcz committed Sep 20, 2024
1 parent cd0bd3d commit 55c1081
Show file tree
Hide file tree
Showing 3 changed files with 382 additions and 0 deletions.
26 changes: 26 additions & 0 deletions xls/modules/zstd/memory/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,32 @@ place_and_route(
target_die_utilization_percentage = "10",
)

py_test(
name = "axi_writer_cocotb_test",
srcs = ["axi_writer_cocotb_test.py"],
data = [
":axi_writer.v",
":axi_writer_wrapper.v",
"@com_icarus_iverilog//:iverilog",
"@com_icarus_iverilog//:vvp",
],
imports = ["."],
tags = ["manual"],
visibility = ["//xls:xls_users"],
deps = [
requirement("cocotb"),
requirement("cocotbext-axi"),
requirement("pytest"),
"//xls/common:runfiles",
"//xls/modules/zstd/cocotb:channel",
"//xls/modules/zstd/cocotb:utils",
"//xls/modules/zstd/cocotb:xlsstruct",
"@com_google_absl_py//absl:app",
"@com_google_absl_py//absl/flags",
"@com_google_protobuf//:protobuf_python",
],
)

xls_dslx_library(
name = "axi_stream_add_empty_dslx",
srcs = ["axi_stream_add_empty.x"],
Expand Down
237 changes: 237 additions & 0 deletions xls/modules/zstd/memory/axi_writer_cocotb_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
#!/usr/bin/env python
# Copyright 2024 The XLS Authors
#
# 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.


import random
import logging
from pathlib import Path

import cocotb
from cocotb.clock import Clock
from cocotb.triggers import ClockCycles, Event
from cocotb.binary import BinaryValue
from cocotb_bus.scoreboard import Scoreboard

from cocotbext.axi.axis import AxiStreamSource, AxiStreamBus, AxiStreamFrame
from cocotbext.axi.axi_channels import AxiAWBus, AxiWBus, AxiBBus, AxiWriteBus, AxiAWMonitor, AxiWMonitor, AxiBMonitor
from cocotbext.axi.axi_ram import AxiRamWrite
from cocotbext.axi.sparse_memory import SparseMemory

from xls.modules.zstd.cocotb.channel import (
XLSChannel,
XLSChannelDriver,
XLSChannelMonitor,
)
from xls.modules.zstd.cocotb.utils import reset, run_test
from xls.modules.zstd.cocotb.xlsstruct import XLSStruct, xls_dataclass

ID_WIDTH = 4
ADDR_WIDTH = 16

@xls_dataclass
class AxiWriterRespStruct(XLSStruct):
status: 1

@xls_dataclass
class WriteRequestStruct(XLSStruct):
address: ADDR_WIDTH
length: ADDR_WIDTH

def set_termination_event(monitor, event, transactions):
def terminate_cb(_):
if monitor.stats.received_transactions == transactions:
event.set()
monitor.add_callback(terminate_cb)

@cocotb.test(timeout_time=20000, timeout_unit="ms")
async def ram_test(dut):
GENERIC_ADDR_REQ_CHANNEL = "write_req"
GENERIC_ADDR_RESP_CHANNEL = "write_resp"
AXI_STREAM_CHANNEL = "axi_st_read"
AXI_AW_CHANNEL = "axi_aw"
AXI_W_CHANNEL = "axi_w"
AXI_B_CHANNEL = "axi_b"

terminate = Event()

mem_size = 2**ADDR_WIDTH
test_count = 200

(addr_req_input, axi_st_input, addr_resp_expect, memory_verification, expected_memory) = generate_test_data_random(test_count, mem_size)

dut.rst.setimmediatevalue(0)

clock = Clock(dut.clk, 10, units="us")
cocotb.start_soon(clock.start())

resp_bus = XLSChannel(dut, GENERIC_ADDR_RESP_CHANNEL, dut.clk, start_now=True)

driver_addr_req = XLSChannelDriver(dut, GENERIC_ADDR_REQ_CHANNEL, dut.clk)
driver_axi_st = AxiStreamSource(AxiStreamBus.from_prefix(dut, AXI_STREAM_CHANNEL), dut.clk, dut.rst)

bus_axi_aw = AxiAWBus.from_prefix(dut, AXI_AW_CHANNEL)
bus_axi_w = AxiWBus.from_prefix(dut, AXI_W_CHANNEL)
bus_axi_b = AxiBBus.from_prefix(dut, AXI_B_CHANNEL)
bus_axi_write = AxiWriteBus(bus_axi_aw, bus_axi_w, bus_axi_b)

monitor_addr_req = XLSChannelMonitor(dut, GENERIC_ADDR_REQ_CHANNEL, dut.clk, WriteRequestStruct)
monitor_addr_resp = XLSChannelMonitor(dut, GENERIC_ADDR_RESP_CHANNEL, dut.clk, AxiWriterRespStruct)
monitor_axi_aw = AxiAWMonitor(bus_axi_aw, dut.clk, dut.rst)
monitor_axi_w = AxiWMonitor(bus_axi_w, dut.clk, dut.rst)
monitor_axi_b = AxiBMonitor(bus_axi_b, dut.clk, dut.rst)

set_termination_event(monitor_addr_resp, terminate, test_count)

memory = AxiRamWrite(bus_axi_write, dut.clk, dut.rst, size=mem_size)

log = logging.getLogger("cocotb.tb")
log.setLevel(logging.WARNING)
memory.log.setLevel(logging.WARNING)
driver_axi_st.log.setLevel(logging.WARNING)

scoreboard = Scoreboard(dut)
scoreboard.add_interface(monitor_addr_resp, addr_resp_expect)

await reset(dut.clk, dut.rst, cycles=10)
await cocotb.start(driver_addr_req.send(addr_req_input))
await cocotb.start(drive_axi_st(driver_axi_st, axi_st_input))
await terminate.wait()

for bundle in memory_verification:
memory_contents = bytearray(memory.read(bundle["base_address"], bundle["length"]))
expected_memory_contents = bytearray(expected_memory.read(bundle["base_address"], bundle["length"]))
assert memory_contents == expected_memory_contents, "{} bytes of memory contents at base address {}:\n{}\nvs\n{}\nHEXDUMP:\n{}\nvs\n{}".format(hex(bundle["length"]), hex(bundle["base_address"]), memory_contents, expected_memory_contents, memory.hexdump(bundle["base_address"], bundle["length"]), expected_memory.hexdump(bundle["base_address"], bundle["length"]))

@cocotb.coroutine
async def drive_axi_st(driver, inputs):
for axi_st_input in inputs:
await driver.send(axi_st_input)

def generate_test_data_random(test_count, mem_size):
AXI_AXSIZE_ENCODING_MAX_4B_TRANSFER = 2 # Must be in sync with AXI_AXSIZE_ENCODING enum in axi.x

addr_req_input = []
axi_st_input = []
addr_resp_expect = []
memory_verification = []
memory = SparseMemory(mem_size)

random.seed(1234)

for i in range(test_count):
xfer_addr = random.randrange(0, mem_size)
# Don't allow unaligned writes
xfer_addr_aligned = (xfer_addr // 4) * 4
# Make sure we don't write beyond available memory
memory_size_max_xfer_len = mem_size - xfer_addr_aligned
arbitrary_max_xfer_len = 0x5000 # 20kB
xfer_max_len = min(arbitrary_max_xfer_len, memory_size_max_xfer_len)
xfer_len = random.randrange(1, xfer_max_len)
transfer_req = WriteRequestStruct(
address = xfer_addr_aligned,
length = xfer_len,
)
addr_req_input.append(transfer_req)

data_to_write = random.randbytes(xfer_len)
axi_st_frame = AxiStreamFrame(tdata=data_to_write, tkeep=[15]*xfer_len, tid=(i % (1 << ID_WIDTH)), tdest=(i % (1 << ID_WIDTH)))
axi_st_input.append(axi_st_frame)

write_expected_memory(transfer_req, axi_st_frame.tdata, memory)

memory_bundle = {
"base_address": transfer_req.address,
"length": transfer_req.length,
}
memory_verification.append(memory_bundle)

addr_resp_expect = [AxiWriterRespStruct(status=False)] * test_count

return (addr_req_input, axi_st_input, addr_resp_expect, memory_verification, memory)

def bytes_to_4k_boundary(addr):
AXI_4K_BOUNDARY = 0x1000
return AXI_4K_BOUNDARY - (addr % AXI_4K_BOUNDARY)

def write_expected_memory(transfer_req, data_to_write, memory):
"""
Write test data to reference memory keeping the AXI 4kb boundary
by spliting the write requests into smaller ones.
"""
prev_id = 0
address = transfer_req.address
length = transfer_req.length

BYTES_IN_TRANSFER = 4
MAX_AXI_BURST_BYTES = 256 * BYTES_IN_TRANSFER

while (length > 0):
bytes_to_4k = bytes_to_4k_boundary(address)
new_len = min(length, min(bytes_to_4k, MAX_AXI_BURST_BYTES))
new_data = data_to_write[prev_id:prev_id+new_len]
memory.write(address, new_data)
address = address + new_len
length = length - new_len
prev_id = prev_id + new_len

def generate_test_data_arbitrary(mem_size):
AXI_AXSIZE_ENCODING_MAX_4B_TRANSFER = 2 # Must be in sync with AXI_AXSIZE_ENCODING enum in axi.x

addr_req_input = []
axi_st_input = []
addr_resp_expect = []
memory_verification = []
memory = SparseMemory(mem_size)

xfer_addr_begin = [0, 8, 512, 1000, 0x1234, 256]
xfer_len = [1, 2, 4, 8, 0x48d, 4]
assert len(xfer_len) == len(xfer_addr_begin)
testcase_num = len(xfer_addr_begin) # test cases to execute
for i in range(testcase_num):
transfer_req = WriteRequestStruct(
address = xfer_addr_begin[i],
length = xfer_len[i] * 4, # xfer_len[i] transfers per 4 bytes
)
addr_req_input.append(transfer_req)

data_chunks = []
data_bytes = [[(0xEF + j) & 0xFF, 0xBE, 0xAD, 0xDE] for j in range(xfer_len[i])]
data_words = [int.from_bytes(data_bytes[j]) for j in range(xfer_len[i])]
for j in range(xfer_len[i]):
data_chunks += data_bytes[j]
data_to_write = bytearray(data_chunks)
axi_st_frame = AxiStreamFrame(tdata=data_to_write, tkeep=[15]*xfer_len[i], tid=i, tdest=i)
axi_st_input.append(axi_st_frame)

write_expected_memory(transfer_req, axi_st_frame.tdata, memory)

memory_bundle = {
"base_address": transfer_req.address,
"length": transfer_req.length, # 4 byte words
}
memory_verification.append(memory_bundle)

addr_resp_expect = [AxiWriterRespStruct(status=False)] * testcase_num

return (addr_req_input, axi_st_input, addr_resp_expect, memory_verification, memory)

if __name__ == "__main__":
toplevel = "axi_writer_wrapper"
verilog_sources = [
"xls/modules/zstd/memory/axi_writer.v",
"xls/modules/zstd/memory/axi_writer_wrapper.v",
]
test_module=[Path(__file__).stem]
run_test(toplevel, test_module, verilog_sources)
119 changes: 119 additions & 0 deletions xls/modules/zstd/memory/axi_writer_wrapper.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2024 The XLS Authors
//
// 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.

`default_nettype none

module axi_writer_wrapper (
input wire clk,
input wire rst,

output wire write_resp_data,
output wire write_resp_vld,
input wire write_resp_rdy,

input wire [31:0] write_req_data,
input wire write_req_vld,
output wire write_req_rdy,

input wire [31:0] axi_st_read_tdata,
input wire [3:0] axi_st_read_tstr,
input wire [3:0] axi_st_read_tkeep,
input wire [0:0] axi_st_read_tlast,
input wire [3:0] axi_st_read_tid,
input wire [3:0] axi_st_read_tdest,
input wire axi_st_read_tvalid,
output wire axi_st_read_tready,

output wire [3:0] axi_aw_awid,
output wire [15:0] axi_aw_awaddr,
output wire [2:0] axi_aw_awsize,
output wire [7:0] axi_aw_awlen,
output wire [1:0] axi_aw_awburst,
output wire axi_aw_awvalid,
input wire axi_aw_awready,

output wire [31:0] axi_w_wdata,
output wire [3:0] axi_w_wstrb,
output wire [0:0] axi_w_wlast,
output wire axi_w_wvalid,
input wire axi_w_wready,

input wire [1:0] axi_b_bresp,
input wire [3:0] axi_b_bid,
input wire axi_b_bvalid,
output wire axi_b_bready

);

wire [32:0] axi_writer__ch_axi_aw_data;
wire [46:0] axi_writer__ch_axi_w_data;
wire [ 6:0] axi_writer__ch_axi_b_data;

wire [15:0] write_req_data_address;
wire [15:0] write_req_data_length;

wire [48:0] axi_st_read_data;

assign {write_req_data_address, write_req_data_length} = write_req_data;

assign { axi_aw_awid,
axi_aw_awaddr,
axi_aw_awsize,
axi_aw_awlen,
axi_aw_awburst } = axi_writer__ch_axi_aw_data;

assign {axi_w_wdata, axi_w_wstrb, axi_w_wlast} = axi_writer__ch_axi_w_data;

assign axi_writer__ch_axi_b_data = {axi_b_bresp, axi_b_bid};

assign axi_st_read_data = {
axi_st_read_tdata,
axi_st_read_tstr,
axi_st_read_tkeep,
axi_st_read_tlast,
axi_st_read_tid,
axi_st_read_tdest
};

axi_writer axi_writer (
.clk(clk),
.rst(rst),

.axi_writer__ch_write_req_data(write_req_data),
.axi_writer__ch_write_req_rdy (write_req_rdy),
.axi_writer__ch_write_req_vld (write_req_vld),

.axi_writer__ch_write_resp_rdy (write_resp_rdy),
.axi_writer__ch_write_resp_vld (write_resp_vld),
.axi_writer__ch_write_resp_data(write_resp_data),

.axi_writer__ch_axi_aw_data(axi_writer__ch_axi_aw_data),
.axi_writer__ch_axi_aw_rdy (axi_aw_awready),
.axi_writer__ch_axi_aw_vld (axi_aw_awvalid),

.axi_writer__ch_axi_w_data(axi_writer__ch_axi_w_data),
.axi_writer__ch_axi_w_rdy (axi_w_wready),
.axi_writer__ch_axi_w_vld (axi_w_wvalid),

.axi_writer__ch_axi_b_data(axi_writer__ch_axi_b_data),
.axi_writer__ch_axi_b_rdy (axi_b_bready),
.axi_writer__ch_axi_b_vld (axi_b_bvalid),

.axi_writer__ch_axi_st_read_data(axi_st_read_data),
.axi_writer__ch_axi_st_read_rdy (axi_st_read_tready),
.axi_writer__ch_axi_st_read_vld (axi_st_read_tvalid)
);


endmodule : axi_writer_wrapper

0 comments on commit 55c1081

Please sign in to comment.