for还是foreach?绿皮书第二章

1 for or foreach

在 Verilog 中只有for循环,但在 SV 中,foreach是绝对的霸主

why:

使用传统的for循环,你必须手动声明循环变量(如int integer i),并且必须先通过.size()$size()获取数组的边界,一旦写错边界(比如多算或少算 1)就会导致数组越界(Array Out of Bounds)。 而foreach自动检测数组的维度和大小,自动创建局部循环变量,绝对不会发生越界。

int my_array[10]; //定宽数组 10个整数[0]...[9] int i; //传统 for(i = 0; i<$size(my_array); i++)begin my_array[i] = i*2; end //SV 的 foreach写法 foreach (my_array[idx])begin my_array[idx] = i*2; // idx 是自动创建的局部变量,代表当前的索引 end

如果是二维数组 int matrix[2][3], 用foreach怎么遍历?

foreach(matrix[i,j]) i行j列二维

练习

foreach遍历关联数组的顺序:

在 SystemVerilog 中,当关联数组的索引(Key)是数值类型(如int,bit等)时,foreach遍历它是严格按照索引从小到大。

如果关联数组的索引是字符串(如int score[string])呢?

foreach会按照字母表(ASCII码)升序的顺序去遍历它(例如"apple"$\rightarrow$"banana"$\rightarrow$"orange")。

结果:

2 关联数组

why:

如果我们要验证一个拥有4GB 寻址空间的 DDR 控制器,如果用普通的动态数组,仿真器会直接尝试在你的 Linux 服务器内存里开辟 4GB 的空间,你的服务器会瞬间崩溃(Out of Memory)。 但实际上,一个测试用例可能一共只往这个 DDR 里写了 10 个不同的地址。

关联数组就是“用多少,分配多少”。它在内存中是用树状结构(Hash表)存储的,只为你真正写入的地址分配空间。

数据类型 数组名[索引类型]

//声明方式 : 数据类型 数组名[索引类型] bit [31:0] mem[bit [31:0]]; //数据32位,地址索引也是32位的关联数组 initial begin mem[32'h0000_1000] = 32'hAAAA_BBBB; //写入地址0x1000 mem[32'hFFFF_E000] = 32'h1111_BBBB; //写入地址0xFFFF_E000 end

此时内存中只存了这2个元素。

怎么知道某个地址有没有被写入过?

不能直接去读,读一个不存在的键会返回默认值。

使用.exists()方法:if (mem.exists(32'h0000_1000))

3 typedef 用户自定义类型

why:

在大型项目里,我们不希望到处写bit [31:0]unsigned int。一旦某天总线位宽从 32 位升级到 64 位,我们需要改动成百上千处代码。 通过typedef,我们把类型抽象化,不仅提高了可读性,更方便了后续的维护。

例子:

1

typedef bit [31:0] data_t; typedef bit [63:0] addr_t; data_t write_data; //声明时使用我们自定义的类型 addr_t target-addr;

2

3

定义了一个32位int型的 fixed_array5(新的类型),它是一个有五个元素的定宽数组。

声明f5这么一个定宽数组, 进行初始化。

4 枚举类型 enum

在verilog中,为了表示状态机的状态,我们会使用parameter或者`define ,但这有很大的弊端:它们说到底只是普通的整数,仿真器在波形里只会显示0, 1, 2`,Debug 时非常痛苦。

why:

1.强类型检查:不允许把一个无意义的整数直接赋给枚举变量,防止赋值出错。

2.verdi波形直观显示:可以直接显示文本IDLE

写法:

typedef enum bit[1:0]{ NOP = 2'b00, READ = 2'b01, WRITE= 2'b10 } cmd_e; cmd_e my_cmd;

如果写my_cmd = 1;编译能通过吗?

不能!1是一个普通的int, my_cmd是cmd_e枚举类型。

正确赋值方法 my_cmd=READ; 或者 进行类型转换 : my_cmd = cmd_t'(1);

练习:


结果

在 SV 中,如果没有显式指定数值,枚举类型的第一个元素(IDLE)默认是0,往后依次递增。它的默认数据类型是int。

如果定义typedef enum {A=3, B, C=9, D} test_e;,那么BD的值是多少?

B会在前A=3 基础上加1变成4;D变成10.

5 const常量

why:

在 Verilog 中,我们通常使用parameter。在 SV 中,如果我们希望在仿真开始运行时(Run-time)动态计算一个值,且在后续仿真中不允许任何代码修改它,我们就会用const

写法:

const bit [7:0] mask = 8'hFF;

6 string 字符串

why:

在验证中,我们经常处理各种文本(根据当前测试用例名字去寻找对应的输入文件,打印格式化log) SV专门引入了string类型。

重点:

string msg = $sformatf("Addr is %0h, Data is %0h", addr, data);