• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

SOC/IP验证工程师

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

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() 等方法,可以获取、匹配并解析命令行参数,无需你从零开始遍历和拆分。

💎 总结与建议

  1. 首选 $sscanf:对于结构清晰的字符串,这是最直接高效的方法。
  2. 复杂解析用正则或第三方库:处理模式不固定但符合某种规律的数据时,正则表达式或 svlib 等库能极大简化逻辑。
  3. 手动解析最灵活:当上述方法都无法满足需求时,通过 substr 和 find 等基础操作从头实现一个解析器是最后的选择。

具体选择哪种方法,取决于你字符串的复杂度和项目的需求。

posted on 2026-06-20 14:40  SOC验证工程师  阅读(30)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3