如何用SystemVerilog/Verilog读取文件中的可变长度字符串
处理可变长度事务文件的SystemVerilog实现方案
这个问题在硬件验证里挺常见的——要解析包含可变参数的Write/Read指令文件对吧?用SystemVerilog来实现非常灵活,核心思路是先把每行拆成独立的指令和参数token,再根据指令类型(Write/Read)动态处理后续的可变参数。我给你一套完整的可运行方案,附带详细解释:
核心实现思路
- 拆分每行成Token:用SystemVerilog的
$strtok函数把整行文本按空格拆分成独立的字符串单元,方便逐个解析。 - 区分指令类型:遍历Token时,识别
Write/Read指令,分别处理后续参数:Read指令仅需读取紧跟的地址参数Write指令先读地址,再读取后续所有非指令类Token作为数据,直到遇到下一个指令或行结束
- 动态存储数据:用动态数组存储可变长度的Write数据,避免固定长度限制。
完整代码示例
module variable_length_file_reader; // 定义事务类型枚举,增强代码可读性 typedef enum {WRITE, READ} txn_type_e; // 定义事务结构体,存储完整的指令信息 typedef struct { txn_type_e txn_type; bit[31:0] address; bit[31:0] data[]; // 动态数组适配可变长度数据 } transaction_t; transaction_t txn_queue[$]; // 队列存储解析后的所有事务 initial begin int file_handle; string line_buffer; string token_list[$]; string current_token; int token_index; // 打开目标文件,添加错误检查 file_handle = $fopen("transaction.txt", "r"); if (file_handle == 0) begin $error("Failed to open transaction file! Check file path."); $finish; end // 逐行读取文件内容 while ($fgets(line_buffer, file_handle)) begin // 清理行内的换行符和首尾空格,跳过空行 line_buffer = line_buffer.strip(); if (line_buffer == "") continue; // 将当前行拆分为token数组 token_list.delete(); current_token = $strtok(line_buffer, " "); while (current_token != "") begin token_list.push_back(current_token); current_token = $strtok("", " "); // 继续拆分剩余内容 end // 遍历token数组,解析每个事务 token_index = 0; while (token_index < token_list.size()) begin transaction_t current_txn; if (token_list[token_index] == "Write") begin current_txn.txn_type = WRITE; // 解析地址参数(十六进制格式) $sscanf(token_list[token_index+1], "%h", current_txn.address); token_index += 2; // 读取后续所有数据,直到遇到下一个指令或行结束 current_txn.data = new[0]; while (token_index < token_list.size() && token_list[token_index] != "Write" && token_list[token_index] != "Read") begin bit[31:0] data_value; $sscanf(token_list[token_index], "%h", data_value); current_txn.data = {current_txn.data, data_value}; // 动态扩展数组 token_index += 1; end txn_queue.push_back(current_txn); end else if (token_list[token_index] == "Read") begin current_txn.txn_type = READ; $sscanf(token_list[token_index+1], "%h", current_txn.address); current_txn.data = new[0]; // Read指令无数据 txn_queue.push_back(current_txn); token_index += 2; end else begin $warning("Unknown token: %s at position %0d, skipping...", token_list[token_index], token_index); token_index += 1; end end end // 关闭文件 $fclose(file_handle); // 打印解析结果,验证正确性 $display("\n=== Parsed Transactions ==="); foreach(txn_queue[i]) begin $display("\nTransaction %0d:", i); $display(" Type: %s", txn_queue[i].txn_type.name()); $display(" Address: 0x%h", txn_queue[i].address); if (txn_queue[i].txn_type == WRITE) begin $display(" Data Length: %0d", txn_queue[i].data.size()); foreach(txn_queue[i].data[j]) begin $display(" Data%0d: 0x%h", j, txn_queue[i].data[j]); end end end $finish; end endmodule
关键细节说明
- 动态数组
data[]:自动适配Write指令的可变数据长度,无需预先定义固定大小 $strtok函数:SystemVerilog原生的字符串分割工具,完美解决空格分隔的文本拆分问题- 事务队列
txn_queue[$]:将解析后的事务统一存储,方便后续对接DUT驱动或验证检查流程 - 错误处理:包含文件打开失败检查、未知token警告,让代码更健壮
关于Verilog的补充说明
如果必须用传统Verilog(非SystemVerilog)实现,会受限较多:没有动态数组、字符串操作和队列,只能通过逐字符解析或固定长度数组模拟,实现复杂度会高很多,因此优先推荐使用SystemVerilog方案。
内容的提问来源于stack exchange,提问作者Vishwasu Deshpande
相关产品推荐
相关产品推荐

