File I/O và VCD: dữ liệu đọc được phải được kiểm

Khoảng 35 phútMức 2/3Bản nháp: một số khẳng định chưa đối chiếu spec

Sau bài này bạn sẽ

  • Kiểm descriptor và số trường parse trước khi dùng vector
  • Tách dòng lỗi, thiếu record và EOF khỏi kết quả tính DUT
  • Chọn scope VCD có mục tiêu và phân biệt tạo file với kiểm trace
  • Giữ expected file độc lập với output DUT và tránh dùng lại dữ liệu cũ

Mở được file chưa có nghĩa vector hợp lệ

Một checker đọc thiếu trường có thể giữ expected của dòng trước rồi vẫn so sánh như chưa có chuyện gì. Lúc ấy regression xanh không nói được DUT đúng: bản thân luồng nhập test đã sai.

Ví dụ dùng ba record hexadecimal gồm a, b và tổng 5 bit đã tính tay. Nó kiểm trạng thái mở file, số dòng, số trường, kết quả DUT và EOF, rồi tạo một VCD chỉ chứa scope DUT để phục vụ debug.

Kiểm file descriptor và đóng đúng lúc

$fopen trả descriptor dùng cho các thao tác sau;0 là dấu hiệu mở thất bại cần xử lý trước khi đọc/ghi. TB mở vectors.txt ở mode w, ghi ba dòng, đóng rồi mới mở lại mode r.

Mode w ghi lại file, vì vậy đường dẫn phải là output tạm do test sở hữu. Pipeline chạy trong working directory tạm; ví dụ không trỏ tới file người dùng. Ở test thật đọc vector do model khác sinh, không thay nó bằng output DUT để làm expected. [1]

Đọc dòng trước, kiểm số trường sau

Mỗi lượt dùng fgets lấy một dòng vào buffer, yêu cầu số byte khác 0, rồi sscanf parse ba số hex. Format còn thử đọc một trường text thứ tư; chỉ chấp nhận đúng 3 trường, nên dòng có dữ liệu dư cũng không được coi là record hợp lệ.

Expected phải là giá trị đã biết. Case inequality kiểm tổng DUT với expected; không chạy phép so sánh khi parse chưa đủ trường. Dòng 0f nope 10 được thử riêng: chỉ đọc được trường đầu, nên phải được nhận diện malformed bằng số trường 1.

Buffer có giới hạn 128 ký tự; các record của ví dụ rất ngắn. Parser cho file lớn phải xử lý cả dòng quá dài, comment và schema mong muốn, không giả định ví dụ này đã phủ hết. [2]

EOF không phải một record có giá trị 0

Sau đúng ba record, TB thử fgets lần nữa và yêu cầu 0 byte cùng EOF đã bật. Không đem buffer cũ ra dùng như record mới. Nếu thiếu dòng, kiểm trước parse phải fail; nếu có dòng thứ tư, kiểm EOF phải fail.

Trong bản thử ban đầu dùng fscanf liên tiếp, Icarus trả 0 trường ở lần đọc sau newline cuối và EOF đã bật. Bài đổi sang đọc theo dòng để tách rõ “không có dòng” với “có dòng nhưng parse sai”, thay vì coi mọi rc khác 3 là một EOF hợp lệ.

Điểm tổng quát là phải hiểu return value của đúng hàm và kiểm record count. Không dùng một vòng while chỉ nhìn feof rồi mặc định mọi lần đọc đều thành công. [3]

Expected đến từ file có nguồn độc lập

Ba phép tính trước chạy là 0+0=0,15+1=16 và 10+5=15. Carry được giữ bằng tổng 5 bit; record 15+1 bắt trường hợp vô tình chỉ giữ 4 bit làm tổng thành 0.

TB tự ghi file hằng số để ví dụ có thể chạy độc lập; đó không phải việc lấy output DUT để tự chấm. Ở flow thực, model phần mềm có thể tạo nhiều record, nhưng vẫn cần biết model và RTL có chia sẻ cùng lỗi thuật toán không.

Ba vector minh họa luồng I/O, không phải vét cạn bộ cộng 4 bit. Các bài A6 đã có kiểm vét cạn riêng; đừng nâng phạm vi bài này chỉ vì nó đọc từ file.

Dump đúng scope và đúng mục tiêu debug

Dumpfile đặt tên lesson.vcd, dumpvars chọn toàn hierarchy dưới DUT bằng mức 0. Đây là chọn scope ghi tín hiệu, không phải lệnh tự chứng minh waveform đúng. Để file gọn hơn, chọn tín hiệu hoặc độ sâu phù hợp câu hỏi debug.

Sau khi dữ liệu đã chạy, TB gọi dumpflush và kiểm VCD mở được, có ký tự mở đầu header như Icarus tạo. Mục đích là xác nhận có output để xem; kiểm prefix không chứng minh mọi timestamp hoặc lane đúng. [4]

Pipeline Icarus hiện chưa tích hợp trace extraction; example chỉ claim parsed/simulated, không claim traced. Những wave khác gắn origin simulated phải qua cơ chế đối chiếu riêng của dự án.

Ba record và một dòng malformed

Kỳ vọng trước chạy: ba tổng 0/16/15, mỗi dòng đủ 3 trường; sau ba dòng là EOF; malformed chỉ có 1 trường; VCD được tạo và checker kết thúc tại 3 ns.

File vectors có kiểm parse và VCD có scope

Kỳ vọng trước chạy: ba record 0+0=0,15+1=16,10+5=15; fgets/sscanf đủ 3 trường mỗi record, EOF fgets 0/feof 1; dòng 0 f nope 10 chỉ parse 1 trường.

tb_files.sv testbench
module file_adder(input wire [3:0] a, b, output wire [4:0] sum);  timeunit 1ns;  timeprecision 1ps;  assign sum = {1'b0, a} + {1'b0, b};endmodulemodule tb_files;  timeunit 1ns;  timeprecision 1ps;  reg [3:0] a, b, bad_a, bad_b;  reg [4:0] expected_sum, bad_expected;  wire [4:0] sum;  integer fd, rc, row, vcd_fd, first_char;  reg [8*128-1:0] line;  reg [8*32-1:0] extra;  file_adder dut(.a(a), .b(b), .sum(sum));  initial begin    $dumpfile("lesson.vcd");    $dumpvars(0, tb_files.dut);    fd = $fopen("vectors.txt", "w");    if (fd == 0) $fatal(1, "cannot create vectors.txt");    // Independent hand-calculated records: 0+0=0,15+1=16,10+5=15.    $fdisplay(fd, "0 0 00");    $fdisplay(fd, "f 1 10");    $fdisplay(fd, "a 5 0f");    $fclose(fd);    fd = $fopen("vectors.txt", "r");    if (fd == 0) $fatal(1, "cannot reopen vectors.txt");    for (row = 0; row < 3; row = row + 1) begin      rc = $fgets(line, fd);      if (rc == 0) $fatal(1, "missing vector row=%0d", row+1);      rc = $sscanf(line, "%h %h %h %s", a, b, expected_sum, extra);      if (rc != 3 || (^expected_sum === 1'bx)) $fatal(1, "invalid vector row=%0d fields=%0d", row+1, rc);      #1;      if (sum !== expected_sum) $fatal(1, "file vector mismatch row=%0d", row+1);      $display("FILE ROW n=%0d a=%0d b=%0d sum=%0d", row+1, a, b, sum);    end    rc = $fgets(line, fd);    if (rc != 0 || !$feof(fd)) $fatal(1, "expected EOF after three rows bytes=%0d eof=%0d", rc, $feof(fd));    $fclose(fd);    rc = $sscanf("0f nope 10", "%h %h %h", bad_a, bad_b, bad_expected);    if (rc != 1) $fatal(1, "malformed row was not detected");    $display("FILE CHECK rows=3 eof=1 malformed_fields=%0d", rc);    $dumpflush;    vcd_fd = $fopen("lesson.vcd", "r");    if (vcd_fd == 0) $fatal(1, "missing VCD");    first_char = $fgetc(vcd_fd);    if (first_char != 36) $fatal(1, "VCD missing expected header prefix");    $fclose(vcd_fd);    $display("VCD CHECK created=1 header_prefix=dollar at=%0d", $time);    $finish;  endendmodule

Đã kiểm chứng thế nào

  • Phân tích và elaborate theo LRM (pyslang 12.1.0)
  • Mô phỏng, exit 0 và output khớp kỳ vọng tính tay (Icarus Verilog 11.0, 4-state)

Output phải khớp: FILE ROW n=1 a=0 b=0 sum=0FILE ROW n=2 a=15 b=1 sum=16FILE ROW n=3 a=10 b=5 sum=15FILE CHECK rows=3 eof=1 malformed_fields=1VCD CHECK created=1 header_prefix=dollar at=3

VCD chỉ dump DUT scope và TB kiểm file được tạo/header prefix. Không claim traced hoặc waveform pipeline đã đối chiếu từng ô; Icarus path chưa tích hợp trace extraction.

File runtime tạo trong working directory tạm của pipeline, không dùng dữ liệu hay đường dẫn riêng của người dùng. Không claim mọi lỗi filesystem.

Các kiểm tra chạy lúc xây dựng nội dung (2026-11-06), không chạy trong trình duyệt. Kỳ vọng được tính tay trước khi chạy.

Assertions kiểm dữ liệu và điều kiện nhập trước khi in summary. Runtime file nằm trong thư mục tạm; không commit vector sinh ra hoặc VCD thay cho source/bằng chứng. [5]

Đừng gọi file tồn tại là trace đã xác minh

  • Không kiểm descriptor 0 rồi dùng một handle mở thất bại.
  • Đọc thiếu trường nhưng tái sử dụng biến của record trước.
  • Coi mọi parse failure là EOF, làm bỏ qua malformed record.
  • Chỉ kiểm tổng số dòng mà không so dữ liệu mỗi dòng.
  • Không giữ carry khi đọc expected hoặc tính sum.
  • Dump toàn chip khi chỉ cần một scope nhỏ.
  • Coi VCD có header là đã đối chiếu waveform từng ô.

Tự kiểm tra

Tự kiểm tra: tb-io

  1. Sscanf dòng 0 f nope 10 theo ba trường hex trong TB đọc được bao nhiêu trường?
  2. Record a=f, b=1 cần expected 5 bit bằng bao nhiêu?
  3. Sau đúng 3 dòng, kiểm fgets nào xác nhận EOF theo hợp đồng ví dụ?
  4. TB mở được VCD và thấy header prefix. Có đủ để claim traced từng waveform cell không?
  5. Fopen trả 0 trước khi đọc vector. Checker nên làm gì?

Kiểm cả đường dữ liệu lẫn đường nhập dữ liệu

Một vector chỉ được dùng sau khi mở file, đọc dòng và parse đều đạt hợp đồng. Xác nhận đủ record, EOF và lỗi định dạng trước khi diễn giải kết quả DUT.

Bài giữ draft/LRM needs-spec-check; file I/O và VCD được mô phỏng thật trong phạm vi nêu rõ.

Nguồn và mức độ đối chiếu

  1. Verilog File IO Operations — bản chụp trong resources.zip, Opening and closing files. Trích dẫn; nguồn đã đọc các mục liên quan.
  2. Verilog File IO Operations — bản chụp trong resources.zip, Reading a line. Trích dẫn; nguồn đã đọc các mục liên quan.
  3. Verilog File IO Operations — bản chụp trong resources.zip, Detecting EOF. Trích dẫn; nguồn đã đọc các mục liên quan.
  4. Verilog VCD Dump — bản chụp trong resources.zip, dumpfile và dumpvars. Trích dẫn; nguồn đã đọc các mục liên quan.
  5. Thí nghiệm nguyên bản ex.a7.tb-io, Thí nghiệm nguyên bản. Trích dẫn; nguồn đã đọc toàn văn.
  6. IEEE 1800-2023 SystemVerilog LRM, tb-io language and system task semantics. Chưa đối chiếu với văn bản nguồn (chưa mở).
  7. Thí nghiệm nguyên bản ex.a7.tb-io, Source, assertions và verification.json của TB nguyên bản. Trích dẫn; nguồn đã đọc toàn văn.
  8. IEEE 1800-2023 SystemVerilog LRM, File I/O; formatted input; EOF; VCD system tasks. Chưa đối chiếu với văn bản nguồn (chưa mở).
  9. Verilog File IO Operations — bản chụp trong resources.zip, Opening and closing files; How to read files; Reading a line; Detecting EOF. Trích dẫn; nguồn đã đọc các mục liên quan.
  10. Verilog VCD Dump — bản chụp trong resources.zip, $dumpfile; $dumpvars. Trích dẫn; nguồn đã đọc các mục liên quan.
  11. Thí nghiệm nguyên bản ex.a7.tb-io, tb_files.sv và verification.json. Trích dẫn; nguồn đã đọc toàn văn.