近期在写功能模块的sv-ref_model时,想到使用队列来实现会方便很多,搜索队列的使用会有很多帖子,自己也是在前辈的基础上稍作总结,作为自己的记录,也供后续参考。
一、队列的核心概念
队列是一种大小可动态调整的有序集合,特点如下:
- 索引访问:支持像数组一样用索引(
[0]、[1]…)访问元素,索引从 0 开始,$表示最后一个元素的索引。 - 高效增删:在队列的头部和尾部添加 / 删除元素的时间复杂度为 O (1),比动态数组更高效。
- 存储类型:可以存储基础类型(
int、bit、byte、string)或复杂类型(类句柄、结构体)。 - 声明语法:使用
[$]标识,格式为数据类型 队列名[$];。
二、队列的基础操作(核心)
下面通过代码示例展示队列的初始化、增删、访问、清空等基础操作,这些是日常使用中最频繁的操作。
1. 声明与初始化
module test_queue; initial begin // 1. 声明空队列 int q1[$]; string q2[$]; // 2. 声明并初始化队列(用{}赋值,元素用逗号分隔) int q3[$] = {10, 20, 30}; // 初始元素:10、20、30 int q4[$] = '{40, 50, 60}; // 也可以用'{}(SystemVerilog推荐写法) // 3. 打印队列(%p是格式化打印复合类型的占位符) $display("q1(空队列):%p", q1); $display("q3:%p", q3); $finish; end endmodule输出结果:
q1(空队列):'{} q3:'{10, 20, 30}2. 元素的添加(头部 / 尾部)
队列提供了专门的方法用于在头部 / 尾部添加元素,也支持插入到指定位置:
module test_queue; initial begin int q[$] = {1, 2, 3}; $display("初始队列:%p", q); // 1. 尾部添加元素:push_back(最常用) q.push_back(4); // 队列变为:{1,2,3,4} $display("push_back(4)后:%p", q); // 2. 头部添加元素:push_front q.push_front(0); // 队列变为:{0,1,2,3,4} $display("push_front(0)后:%p", q); // 3. 插入到指定索引位置:insert(索引, 元素) q.insert(2, 99); // 在索引2的位置插入99,队列变为:{0,1,99,2,3,4} $display("insert(2,99)后:%p", q); $finish; end endmodule输出结果:
初始队列:'{1, 2, 3} push_back(4)后:'{1, 2, 3, 4} push_front(0)后:'{0, 1, 2, 3, 4} insert(2,99)后:'{0, 1, 99, 2, 3, 4}3. 元素的移出/删除(头部 / 尾部 / 指定位置)
module test_queue; initial begin int q[$] = {0, 1, 99, 2, 3, 4}; $display("初始队列:%p", q); // 1. 尾部移出元素:pop_back(返回被移出的元素) int val1 = q.pop_back(); // 删除4,val1=4,队列变为:{0,1,99,2,3} $display("pop_back后:%p,被删除元素:%0d", q, val1); // 2. 头部移出元素:pop_front(返回被移出的元素) int val2 = q.pop_front(); // 删除0,val2=0,队列变为:{1,99,2,3} $display("pop_front后:%p,被删除元素:%0d", q, val2); // 3. 删除指定索引的元素:delete(索引) q.delete(1); // 删除索引1的99,队列变为:{1,2,3} $display("delete(1)后:%p", q); // 4. 清空整个队列:delete()(无参数) q.delete(); // 队列变为空 $display("delete()后(清空队列):%p", q); $finish; end endmodule输出结果:
初始队列:'{0, 1, 99, 2, 3, 4} pop_back后:'{0, 1, 99, 2, 3},被删除元素:4 pop_front后:'{1, 99, 2, 3},被删除元素:0 delete(1)后:'{1, 2, 3} delete()后(清空队列):'{}4. 元素的访问与长度获取
module test_queue; initial begin int q[$] = {10, 20, 30, 40}; // 1. 通过索引访问元素(索引从0开始,$表示最后一个元素) $display("索引0的元素:%0d", q[0]); // 10 $display("最后一个元素(q[$]):%0d", q[$]); // 40 $display("倒数第二个元素(q[$-1]):%0d", q[$-1]); // 30 // 2. 获取队列长度:size()方法 $display("队列长度:%0d", q.size()); // 4 // 3. 修改指定索引的元素 q[1] = 200; // 索引1的元素从20改为200 $display("修改后队列:%p", q); // '{10,200,30,40} $finish; end endmodule输出结果:
索引0的元素:10 最后一个元素(q[$]):40 倒数第二个元素(q[$-1]):30 队列长度:4 修改后队列:'{10, 200, 30, 40}三、队列的高级操作
1. 队列的切片(截取部分元素)
队列支持用[start:end]的切片语法截取指定范围的元素,返回新的队列:
module test_queue; initial begin int q[$] = {1, 2, 3, 4, 5}; // 1. 截取索引1到3的元素(包含1和3) int q_slice1[$] = q[1:3]; $display("q[1:3]:%p", q_slice1); // '{2,3,4} // 2. 截取从索引2到末尾的元素($表示最后一个索引) int q_slice2[$] = q[2:$]; $display("q[2:$]:%p", q_slice2); // '{3,4,5} // 3. 截取前3个元素(索引0到2) int q_slice3[$] = q[0:2]; $display("q[0:2]:%p", q_slice3); // '{1,2,3} $finish; end endmodule2. 队列的拼接
使用{}运算符可以将多个队列(或单个元素)拼接成新的队列:
module test_queue; initial begin int q1[$] = {1, 2}; int q2[$] = {3, 4}; // 1. 拼接两个队列 int q3[$] = {q1, q2}; $display("q1+q2:%p", q3); // '{1,2,3,4} // 2. 队列与单个元素拼接 int q4[$] = {q1, 99, q2}; $display("q1+99+q2:%p", q4); // '{1,2,99,3,4} // 3. 队列自身拼接(复制两倍) int q5[$] = {q1, q1}; $display("q1复制两倍:%p", q5); // '{1,2,1,2} $finish; end endmodule最近在调试ref_model时要实现最邻近插值就用到了队列拼接功能,将一个队列q1复制两次,拼接在原有队列q2后面,实现语句:q2={q2, q1, q1}; 这样的实现是不是很简明?!
3. 队列的遍历(foreach 循环)
使用foreach循环可以遍历队列的所有元素,是处理队列数据的常用方式:
module test_queue; initial begin int q[$] = {10, 20, 30, 40}; $display("遍历队列元素:"); foreach (q[i]) begin $display("索引%0d的元素:%0d", i, q[i]); end // 遍历的同时修改元素(比如所有元素乘2) foreach (q[i]) begin q[i] = q[i] * 2; end $display("元素乘2后:%p", q); // '{20,40,60,80} $finish; end endmodule输出结果:
遍历队列元素: 索引0的元素:10 索引1的元素:20 索引2的元素:30 索引3的元素:40 元素乘2后:'{20, 40, 60, 80}4. 复杂类型队列(类句柄队列)
队列可以存储类的句柄,常用于验证中存储事务对象(如 AHB/AXI 事务),注意需要使用深拷贝避免浅拷贝问题:
// 定义一个简单的事务类 class Trans; int data; // 深拷贝方法:创建新对象并复制属性 function Trans copy(); copy = new(); copy.data = this.data; endfunction endclass module test_queue; initial begin Trans q[$]; // 声明类句柄队列 Trans t1, t2; // 初始化队列 t1 = new(); t1.data = 100; t2 = new(); t2.data = 200; q.push_back(t1); q.push_back(t2); $display("队列元素值:%0d, %0d", q[0].data, q[1].data); // 100, 200 // 深拷贝:添加新对象到队列(避免浅拷贝) q.push_back(t1.copy()); q[2].data = 999; // 修改新对象,原对象不受影响 $display("修改后:%0d(原对象),%0d(新对象)", t1.data, q[2].data); // 100, 999 $finish; end endmodule输出结果:
队列元素值:100, 200 修改后:100(原对象),999(新对象)队列的类型可以是常用的int、bit、byte、logic等,也可以是自定义的类型,那么队列中的每个元素都是一个自定义的变量。
四、队列的常用场景(验证中)
- 事务存储:测试平台中存储生成的激励事务、监测到的总线事务(如 AHB/AXI 的读写事务)。
- 数据缓冲:在驱动器(Driver)和监视器(Monitor)中缓冲数据,实现异步数据处理。
- 动态数据处理:需要频繁添加 / 删除元素的场景(如过滤无效事务、排序事务)。
五、总结
- 队列的核心特性:SystemVerilog 队列是动态有序集合,支持索引访问,头部 / 尾部增删元素效率高,声明用
[$]。 - 常用基础操作:
操作/方法 说明 添加 push_back() :尾部添加
push_front() :头部添加
移出 pop_back() :尾部移出
pop_front() :头部移出
清空 delete() :删除整个队列
delete(n) :删除指定位置的元素
长度 size() :整个队列内元素个数 - 高级操作:支持切片
[start:end]、拼接{}、foreach遍历,可存储基础类型和复杂类型(类句柄需深拷贝)。 - 应用场景:主要用于验证中的事务存储、数据缓冲和动态数据处理,是 Testbench 编写的核心数据类型之一。
有更多的使用方法和注意事项欢迎大家留言讨论。