SV中关联数组(Associative Array)的用法
在 SystemVerilog 中,关联数组(Associative Array)是一种非常有用的数据结构,它允许使用任意类型的数据作为索引(称为“键”),并将数据存储在对应的稀疏、可变大小的集合中。与定宽数组或动态数组不同,关联数组只为那些实际赋过值的索引分配存储空间,非常适合建模稀疏存储、查找表或字典结构。
🧠 核心含义
-
键-值对存储:每个条目由一个唯一的“键”和一个对应的“值”组成。
-
稀疏性:并非所有可能的索引(如 0..2^32-1)都占用内存,只有被写入的索引才会创建条目。
-
动态增长:添加新条目时,关联数组自动扩大;删除条目时,相应内存被释放。
-
索引类型灵活:键可以是 整型、字符串、类句柄,甚至是通配符或用户自定义类型。
你可以把它想象成 Verilog 中的
reg [7:0] mem [0:1023]的超级升级版——不再局限于连续整数索引,也不要求提前声明大小。
📚 基本语法与声明
// 声明:data_type 是值类型,index_type 是键的类型 data_type array_name [index_type]; // 示例 int dict[string]; // 用字符串索引,值为整数 string rev_map[int]; // 用整数索引,值为字符串 my_class obj_pool[my_class]; // 用类句柄作为索引(键为对象引用) bit [7:0] byte_by_addr[longint]; // 用64位整数索引 real values[shortint]; // 用短整数索引
关联数组不需要声明大小,访问之前未赋值的索引会返回默认值(对于 int 是 0,对于 string 是 "" 等),但不会自动创建条目(除非你在读取时隐式创建——通常不推荐)。
🛠️ 常用内建方法
| 方法 | 说明 |
|---|---|
array.num() |
返回当前数组中条目的个数 |
array.delete() |
删除整个数组或删除指定索引的条目(如 array.delete(idx)) |
array.exists(idx) |
检查给定索引是否存在条目 |
array.first(itr) |
获得第一个索引(用于迭代),通过引用返回 |
array.next(itr) |
获得下一个索引,常用于遍历 |
array.last(itr) |
获得最后一个索引 |
array.prev(itr) |
获得前一个索引 |
💡 详细举例
例1:用字符串键存储学生成绩
program associative_array_demo;
// 声明关联数组:键为字符串(学生姓名),值为整数(分数)
int score[string];
initial begin
// 赋值
score["Alice"] = 95;
score["Bob"] = 87;
score["Carol"] = 92;
$display("Number of students: %0d", score.num()); // 3
// 访问存在的键
$display("Bob's score: %0d", score["Bob"]); // 87
// 检查不存在的键
if (score.exists("Dave"))
$display("Dave's score: %0d", score["Dave"]);
else
$display("Dave not found");
// 遍历所有条目(注意:遍历顺序不保证与插入顺序一致)
begin
string name;
int sc;
if (score.first(name)) begin
do begin
sc = score[name];
$display("%s -> %d", name, sc);
end while (score.next(name));
end
end
// 删除一个条目
score.delete("Bob");
$display("After delete, num = %0d", score.num()); // 2
end
endprogram
例2:用整数键实现稀疏内存模型
模拟一个有 4GB 地址空间的存储器,但只实际存储被写入的地址:
module sparse_memory;
// 键为 64 位地址,值为字节数据
typedef bit [7:0] byte_t;
byte_t mem[longint];
initial begin
// 只写入很少几个地址,关联数组只为这些地址分配空间
mem[64'h0001_0000] = 8'hAA;
mem[64'hFFFF_FFFF] = 8'h55;
mem[64'h1000_0000_0000_0000] = 8'hFF;
$display("Number of stored bytes: %0d", mem.num()); // 3
// 读取未初始化的地址 -> 返回默认值 0,但不会新增条目
$display("mem[0] = %x (still %0d entries)", mem[0], mem.num()); // 0, 3
end
endmodule
例3:用类句柄作为索引(追踪每个对象的特定属性)
class packet;
int id;
function new(int id); this.id = id; endfunction
endclass
module class_key_demo;
packet pkt1, pkt2;
// 键是 packet 句柄,值是到达时间
int arrival_time[packet];
initial begin
pkt1 = new(1);
pkt2 = new(2);
arrival_time[pkt1] = 1000;
arrival_time[pkt2] = 1020;
$display("pkt1 arrival: %0d", arrival_time[pkt1]); // 1000
$display("pkt2 arrival: %0d", arrival_time[pkt2]); // 1020
end
endmodule
例4:使用通配符索引(*)
[*] 表示索引可以是任何积分值(int、bit等),但不推荐在要求代码清晰且类型安全的场景使用:
int mem[*]; // 索引类型未指定,可接受任何积分类型
⚠️ 重要注意事项
-
读取不存在的索引:返回默认值,但不会自动创建条目。如果之后希望删除整个数组或准确计算
num(),这一点很重要。int a[string]; int x = a["hello"]; // x 得到 0,但数组仍然是空的(num() == 0)
-
遍历顺序:关联数组的遍历顺序是不确定的。如果需要有序访问,请使用
sort方法或queue。 -
性能:对于少量条目(< 1000),关联数组非常高效。超大条目时哈希冲突可能影响性能。
-
可比性:只有可比较的类型才能作为键(如整数、字符串、类句柄)。不能使用实数、
unpacked struct等不可直接比较的类型作为键。
🔁 关联数组 vs. 其他数组类型
| 特性 | 定宽数组 | 动态数组 | 关联数组 |
|---|---|---|---|
| 声明时大小 | 需要 | 不需要(运行时定) | 不需要 |
| 索引类型 | 整数 | 整数 | 任意类型(整数/字符串/句柄等) |
| 存储稀疏性 | 全部连续分配 | 全部连续分配 | 仅存储赋值的条目 |
| 动态改变大小 | 否 | 可以(new[]) |
自动 |
| 适用场景 | 固定大小存储 | 连续增长的存储 | 查找表、字典、稀疏映射 |
✅ 总结
-
关联数组 = 键值对的集合,键可以是任何数据类型。
-
用于建模查找表、稀疏存储、配置映射、对象缓存等。
-
方法:
exists,num,delete,first/next用于遍历。 -
内存高效:只为使用的条目分配存储。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)