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;,那么B和D的值是多少?
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);
