UVM/SystemVerilog中提取数字或拆分字符串
在UVM/SystemVerilog中提取数字或拆分字符串,有几种主流且实用的方法:格式化输入 ($sscanf)、手动遍历字符、正则表达式 (uvm_re_match, str.match) 以及第三方库 (svlib)。通常,最简单的是用 $sscanf,而更复杂的模式则适合用正则表达式。
🎯 从字符串中提取数字
这类操作的核心都是在字符串中定位数字模式,并将其转换为数值类型。
-
场景1:格式固定,模式清晰
当你知道数字的精确位置或上下文时,最推荐的方法是使用$sscanf。它类似于C语言中的sscanf。string my_str = "Packet #3 size is 1500B"; int pkt_size; // 使用 %d 来匹配数字 if ($sscanf(my_str, "Packet #3 size is %0dB", pkt_size) == 1) begin $display("Extracted packet size: %0d", pkt_size); // 输出: 1500 end注意:
$sscanf非常适合解析像日志或命令这样的格式化数据。 -
场景2:通过遍历查找任意位置的数字(不指定索引)
当数字出现的位置不固定时,可以通过遍历字符串的每个字符,将数字提取出来。string my_str = "abc123def456"; string num_str = ""; int num; for (int i=0; i<my_str.len(); i++) begin if (my_str[i] >= "0" && my_str[i] <= "9") begin num_str = {num_str, my_str[i]}; end else if (num_str.len() > 0) begin // 遇到非数字,说明之前的数字串结束(若需要可以提前跳出) break; end end num = num_str.atoi(); // 将捕获的字符串转换为整数 $display("Extracted number: %0d", num);注意:这段代码只提取了第一组连续数字
123。利用atoi()可以将数字字符串转换为整数。 -
场景3:在UVM中使用正则表达式匹配
对于更复杂的模式(如从 "value=0xABCD" 提取十六进制数),正则表达式是最强大的工具。UVM提供了uvm_re_match()函数,但它本身只返回是否匹配,不直接返回捕获的内容。若你的仿真器支持扩展,可以:// 假设你的仿真器支持扩展方法 // if (str.match("value=(\\S+)", match_str)) begin // $display("Matched value: %s", match_str); // end注意:具体语法强依赖于仿真器,使用前务必查阅其手册。
✂️ 拆分或解析字符串
拆分操作的核心是按照指定的分隔符,将原字符串切割成多个子串。
-
场景A:使用
$sscanf按固定分隔符拆分(已知字段数)
该方法适用于字段数量固定的字符串。// 示例: 拆分用逗号分隔的命令 string line = "WRITE, 0x1000, 0xA5"; string cmd, addr_str, data_str; int addr, data; // 注意: %s 会读取连续的、非空白字符的序列 if ($sscanf(line, "%s %s %s", cmd, addr_str, data_str) == 3) begin addr = addr_str.atoi(); // 或使用 $sscanf(addr_str, "%0d", addr) data = data_str.atoi(); $display("Cmd: %s, Addr: 0x%0h, Data: 0x%0h", cmd, addr, data); end注意:
%s会读取连续的、非空白字符的序列。 -
场景B:使用
$sscanf一次性拆分并转换(已知字段数)
如果需要直接解析数值,可以用格式符一步完成拆分和转换。// 直接将十六进制字符串转换为数值 string line = "WRITE A0001000 00000000"; string cmd; int addr, data; if ($sscanf(line, "%s %h %h", cmd, addr, data) == 3) begin $display("Cmd: %s, Addr: 0x%0h, Data: 0x%0h", cmd, addr, data); end注意:这里
%h直接将十六进制字符串转换为整数。 -
场景C:编写通用的
split函数(处理任意字段数)
由于原生SystemVerilog没有split,我们可以自己动手实现一个。function automatic void split_str(string input_str, string delim, ref string q[$]); int start = 0, end; q.delete(); // 清空队列 while ( (end = input_str.substr(start, input_str.len()-1).find(delim)) != -1 ) begin if (end != start) // 忽略连续分隔符 q.push_back( input_str.substr(start, start+end-1) ); start += end + delim.len(); end // 添加最后一个子串 if (start < input_str.len()) begin q.push_back( input_str.substr(start, input_str.len()-1) ); end endfunction // 使用示例: string input = "apple,banana,orange,grape"; string parts[$]; split_str(input, ",", parts); foreach(parts[i]) $display("%0d: %s", i, parts[i]);注意:这个函数会处理可能存在的连续分隔符,并将结果存在队列
q中。 -
场景D:使用第三方库(如 Verilab svlib)进行高级解析
对于复杂的字符串处理,使用成熟的第三方库是最佳实践。Verilab 提供的 svlib 是一个专为复杂文本解析设计的、开源的 SystemVerilog 库。它提供了split()、match()、gsub()等功能,且兼容 UVM。更多细节可参考其官方资源,并注意其 License 限制。import svlib_pkg::*; // ... 使用 Str 类的方法 // Str line = Str::create("a,b,c"); // Str parts[$] = line.split(",");
📚 更全面的字符串操作函数
除了上述方法,SystemVerilog 自身也提供了许多基础的字符串操作函数,这些是手动解析或实现更复杂功能的基础。
| 函数/方法 | 描述 | 示例 |
|---|---|---|
len() |
返回字符串长度 | int l = s.len(); |
getc(i) |
返回指定位置的字符 | byte c = s.getc(0); |
substr(i, j) |
提取子字符串 | string t = s.substr(2,5); |
atoi() / atoreal() / atohex() |
将字符串转换为整数/实数/十六进制 | int i = s.atoi(); |
find(string) |
查找子串,返回索引 | int pos = s.find("abc"); |
toupper() / tolower() |
转换为大小写 | s = s.toupper(); |
putc(i, c) |
修改字符串中的字符 | s.putc(0, "A"); |
💎 在UVM中解析命令行参数
作为补充,如果你的目标是解析仿真命令行传入的字符串(如 +my_string=123,abc,def),UVN 自带的 uvm_cmdline_processor 是一个强大的工具。它提供了 get_arg_value() 等方法,可以获取、匹配并解析命令行参数,无需你从零开始遍历和拆分。
💎 总结与建议
- 首选
$sscanf:对于结构清晰的字符串,这是最直接高效的方法。 - 复杂解析用正则或第三方库:处理模式不固定但符合某种规律的数据时,正则表达式或 svlib 等库能极大简化逻辑。
- 手动解析最灵活:当上述方法都无法满足需求时,通过
substr和find等基础操作从头实现一个解析器是最后的选择。
具体选择哪种方法,取决于你字符串的复杂度和项目的需求。
浙公网安备 33010602011771号