NVMe VIP架构:主机功能

描述

上一篇文章中,介绍了一个基本的NVMe VIP测试用例,包括一些基本的设置,发送命令和接收完成。在这里,我们将再看一些NVMe命令,涉及VIP的一些特性和功能。

在这里,您可以了解有关适用于 NVMe 和 PCIe 的 Synopsys VC 验证 IP 的更多信息。

贵宾的(提醒)视图

我们上次简要介绍了这一点。这次我们将更深入地介绍,因此我们将继续参考此图:

PCIe

NVMe VIP 提供了一组功能来帮助测试。其中包括随机化、功能窥探、简化的 PRP 和数据缓冲区处理、内存屏蔽和内置记分板。我们将依次通过另一个示例来查看其中的每一个。

继续我们的测试用例...

继上一篇文章中的“琐碎测试用例”之后(同样,我们没有显示一些任务参数或检查错误),让我们再看几个命令来启动我们的 NVMe 测试用例。

提醒一下:以脚本一词开头的任务是 NVMe 命令。其他(不以脚本开头)是 VIP 状态/控制/配置任务。

 

// We will assume that the PCIe stack is setup and running
bit [63:0] base_addr = 32’h0001_0000;  // Ctlr BAR base addr
dword_t    num_q_entries, ctlr_id;
// Tell the host where the controller has its base address
AllocateControllerID(base_addr, ctlr_id, status);
num_q_entries = 2;
// Create the Admin Completion and Submission Queues
ScriptCreateAdminCplQ(ctlr_id, num_q_entries, status);
ScriptCreateAdminSubQ(ctlr_id, num_q_entries, status);
// Send an “Identify Controller” Command
data_buf_t #(dword_t) identify_buffer;      // identify data
identify_buffer = new(1024);
ScriptIdentify(ctlr_id, 0, identify_buffer, 0, status);

 

我们以调用标识控制器结束了最后一个示例。现在,继续在这一点上,我们读取字节 519:516 以获取有效命名空间 ID 的数量。我们通过 SetNumNamespaces() 调用将其传递给主机 VIP。请注意,我们必须对识别控制器缓冲区中返回的(小端序)数据进行字节交换。

 

int num_ns, nsid, blk_size_pow2, blk_size_in_bytes;
bit [63:0] ns_size_in_blks;
feature_identifier_t feature_id;
nvme_feature_t set_feature;
// We’ll grab the Number of valid namespaces (NN) from the
// identify buffer. Note index converted from bytes to dword.
num_ns = ByteSwap32(identify_buffer[516 >> 2]); // bytes 519:516
// Tell the VIP how many active NSIDs the controller has
SetNumNamespaces(ctlr_id, num_ns, status);

 

接下来,我们读取其中一个命名空间(命名空间 ID=1)的信息。请注意,我们在这里“作弊”了一点,因为我们应该遍历所有有效的命名空间。对于这个例子,我们只假设我们只有 NSID=1。尽管标识调用不采用 PRP 列表,但其主机内存缓冲区可以具有偏移量。如果需要,请选择参数“use_offset=1”。实际偏移通过约束 MIN/MAX_PRP_DWORD_OFFSET_VAR 随机化。

 

// Now send an “Identify Namespace” command for nsid=1
nsid = 1;
use_offset = 1;            // Randomize buffer offset
ScriptIdentify(ctlr_id, nsid, identify_buffer,
               use_offset, status);
// Pull information from format[0]
blk_size_pow2 = ByteSwap32(identify_buffer.GetData(128 >> 2)));
blk_size_pow2 = (blk_size_pow2 >> 16) & 32’hff;  // dword[23:16]
blk_size_in_bytes = 1 << blk_size_pow2;          // Convert
ns_size_in_blks = ByteSwap64({identify_buffer.GetData(8 >> 2),
                              identify_buffer.GetData(12 >> 2)});
// Before we create queues, we need to configure the num queues
// on the controller.
feature_id = FEATURE_NUM_QUEUES;
set_feature = new(feature_id);

 

一旦识别命名空间返回,我们现在有了块大小和命名空间大小。我们使用设置功能设置请求的队列数量。通过 VIP 的功能侦听,这将(透明地)将 VIP 设置为当前支持的提交和完成队列数量(用于以后的检查和错误注入支持)。

接下来的步骤设置命名空间的格式(使用标识命名空间数据结构中的格式 0)。然后,我们更新命名空间信息的 VIP 视图。VIP 需要此命名空间信息来保留每个命名空间的记分板。

 

set_features.SetNumCplQ(2);     // Request number of sub &
set_features.SetNumSubQ(3);     // cpl queues
// Call Set Features command to set the queues on the ctlr
ScriptSetFeatures(ctlr_id, set_features, …, status);
// Note that Set Features Number of Queues command need not
// return the same amount of queues that were requested. We can
// check by examining set_features.GetNumCplQ() and
// GetNumSubQ(), but in this case we’ll just trust it…
// Format the Namespace
sec_erase = 0;        // Don’t use secure erase
pi_md_settings = 0;   // Don’t use prot info or metadata
format_number = 0;    // From Identify NS data structure
ScriptFormatNVM(ctlr_id, nsid, sec_erase, pi_md_settings,
                format_number, …, status);
// Tell the VIP about this NS
SetNamespaceInfo(ctlr_id, nsid, blk_size_in_bytes,
                 ns_size_in_blks, md_bytes_per_blk,
pi_md_settings, 0, status);

 

接下来,我们创建一对 I/O 队列。由于提交队列需要与其一起传递其配套完成队列,因此我们首先创建完成队列。请注意,队列创建例程采用参数重叠群。如果设置了重叠群,则队列将放置在连续内存中,否则将为该队列创建 PRP 列表。除了创建实际队列之外,VIP 还会在队列周围创建围栏,以验证对队列的内存访问。从控制器(例如)从完成队列读取的尝试将被标记为无效的访问尝试。实际队列 ID 是随机的(在法律和用户可配置的约束范围内)。

 

// Create the I/O Queues
num_q_entries = 10;
contig = 1;           // Contiguous queue
ScriptCreateIOCplQ(ctlr_id, num_q_entries,
contig, …, cplq_id, …, status);
contig = 0;           // PRP-based queue
ScriptCreateIOSubQ(ctlr_id, num_q_entries,
contig, cplq_id …, subq_id, …, status);

 

一旦我们创建了 I/O 队列,我们就可以开始执行 I/O.使用 ScriptWrite() 和 ScriptRead() 调用,我们将数据发送到控制器并立即检索相同的数据。数据的底层数据结构(在主机内存中)由 VIP 自动构建。请注意 use_offset 参数(与我们的队列创建任务一样)来控制我们是否生成 PRP 和 PRP 列表偏移量(分别由 MIN/MAX_PRP_DWORD_OFFSET_VAR 和 MIN/MAX_PRP_LIST_DWORD_OFFSET 控制)。由于我们内置了记分板,我们不必比较从写入的数据读取的数据,VIP 正在根据其卷影副本检查返回的数据,该卷影副本正在跟踪成功向控制器写入的 VIP。

 

// Do our I/O write then read with a random LBA/length
data_buf_t #(dword_t) wbuf, rbuf; // Write/Read Data buffers
num_blks = RandBetween(1, ns_size_in_blks);
lba = RandBetween(0, ns_size_in_blks – num_blks);
num_dwords = (blk_size_in_bytes / 4) * num_blks;
wbuf = new(num_dwords);
for (int idx = 0 ; idx < num_dwords ; idx++) // Fill the buffer
   wbuf.SetData(idx, { 16’hdada, idx[15:0] } );
ScriptWrite(ctlr_id, subq_id, lba, nsid, wbuf, num_blks,
            use_offset, …, status);
// We’ll read the same LBA since we know it’s been written
   ScriptRead(ctlr_id, subq_id, lba, nsid, rbuf, num_blks,
              use_offset, …, status);
// Do what you’d like with the rbuf (that’s the data we just read).

 

大功告成!

希望这能让我们完成大部分基础知识。您应该对VIP的操作有很好的感觉。同样,其中许多任务都有更多的参数,允许更多的控制和错误注入,但我们的目标是在不处理更深奥的功能的情况下完成。如果您有VIP手边的VIP,请随意浏览示例:它们应该看起来很熟悉。

审核编辑:郭婷

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分