Blocking vs non-blocking: vì sao shift register 'mất tầng'

Khoảng 20 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ẽ

  • Dự đoán giá trị biến sau một cạnh clock với = và <=
  • Giải thích vì sao <= trong logic tuần tự cho ra đúng số thanh ghi
  • Nhận diện race giữa testbench và DUT

Trực giác

Thanh ghi "chụp ảnh" đầu vào tại cạnh clock. <= mô hình đúng điều đó: mọi vế phải được đọc trước, rồi mới cập nhật. = thì cập nhật ngay, nên câu lệnh sau thấy giá trị mới.

Hai cách viết, hai phần cứng

Shift register 3 tầng: non-blocking vs blocking

Cho thấy blocking trong logic tuần tự làm mất tầng thanh ghi: cả hành vi mô phỏng lẫn phần cứng suy ra.

sh_nba.sv RTL
// Shift register 3 tầng viết đúng: non-blocking. Reset đồng bộ để mọi tầng xác định (kể cả trên simulator 4-state).module sh_nba (input logic clk, input logic rst, input logic d, output logic q);  logic a, b;  always_ff @(posedge clk) begin    if (rst) begin      a <= 1'b0; b <= 1'b0; q <= 1'b0;    end else begin      a <= d;      b <= a;      q <= b;    end  endendmodule
sh_ba.sv RTL
// Cùng ý đồ nhưng dùng blocking: a, b bị "xuyên qua" trong cùng một clock edge.module sh_ba (input logic clk, input logic rst, input logic d, output logic q);  logic a, b;  always @(posedge clk) begin    if (rst) begin      a = 1'b0; b = 1'b0; q = 1'b0;    end else begin      a = d;      b = a;      q = b;    end  endendmodule
tb_shift.sv testbench
// Stimulus đổi bằng non-blocking ngay sau posedge (không race). Cột k của waveform = giá trị sau posedge thứ k (k=0,1,...).module tb_shift;  logic clk = 0, rst, d, q_nba, q_ba;  int col = 0, t_nba = -1, t_ba = -1;  sh_nba u_nba (.clk(clk), .rst(rst), .d(d), .q(q_nba));  sh_ba  u_ba  (.clk(clk), .rst(rst), .d(d), .q(q_ba));  always #5 clk = ~clk;  initial begin    $dumpfile("trace.vcd"); $dumpvars(0, tb_shift);    rst <= 1'b1; d <= 1'b0;    repeat (2) @(posedge clk);     // posedge cột 0 và cột 1 lấy mẫu rst=1    rst <= 1'b0; d <= 1'b1;        // cột 1: d=1, được lấy mẫu ở posedge cột 2    @(posedge clk) d <= 1'b0;      // cột 2: d=0    repeat (6) @(posedge clk);    $display("SHIFT RESULT t_nba=%0d t_ba=%0d", t_nba, t_ba);    $finish;  end  // Giữa mỗi chu kỳ (negedge): ghi cột đầu tiên q lên 1.  always @(negedge clk) begin    if (q_nba === 1'b1 && t_nba < 0) t_nba = col;    if (q_ba  === 1'b1 && t_ba  < 0) t_ba  = col;    col++;  endendmodule

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

  • Phân tích và elaborate theo LRM (pyslang 12.1.0)
  • Lint -Wall (Verilator 5.050)
  • Mô phỏng, exit 0 và output khớp kỳ vọng tính tay (Verilator 5.050, 2-state)
  • Waveform trích từ VCD của lần mô phỏng
  • Tổng hợp, số cell khớp kỳ vọng (Yosys 0.70)

Output phải khớp: SHIFT RESULT t_nba=4 t_ba=2

Kỳ vọng sau tổng hợp: sh_nba: $_*DFF* = 3sh_ba: $_*DFF* = 1

Verilator là simulator 2-state; ví dụ có reset đồng bộ nên mọi tín hiệu hiển thị đều xác định, kết quả không phụ thuộc 2/4-state.

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.

Dự đoán waveform

Dự đoán waveform

d=1 trong cột 1 (được lấy mẫu ở posedge 2). Điền q_nba và q_ba ở các ô bị che.

Trích từ mô phỏng Verilator (VCD) của ex.a7.shift3, không vẽ tay
01234567clkrstdq_nba??q_ba?
ÔDự đoán
q_ba ở cột 2
q_nba ở cột 3
q_nba ở cột 4

Bug lab: race trong testbench

Bug lab

Testbench mong đợi q=5 sau 5 chu kỳ đếm, nhưng trên Verilator nhận q=6. Tìm dòng gây lỗi và nguyên nhân.

Triệu chứng

Output khi chạy bản này: CNT RESULT q=6. Testbench mong đợi q=5 (5 chu kỳ đếm sau khi bỏ reset).

Bản lỗi đã được mô phỏng thật lúc build (Verilator 5.050); triệu chứng ở trên là output thật.

counter.sv rtl, chỉ đọc
module counter (input logic clk, input logic rst_n, input logic en, output logic [3:0] q);  always_ff @(posedge clk or negedge rst_n)    if (!rst_n) q <= '0;    else if (en) q <= q + 1'b1;endmodule

Bước 1. Bấm vào dòng gây lỗi trong tb_race.sv.

Bước 2. Nguyên nhân gốc là gì?

Kiểm tra

Blocking vs non-blocking

  1. Trong sh_ba, ngay trước posedge #1 thì a=b=q=0 và d=1. Ngay sau posedge #1, (a, b, q) bằng bao nhiêu?
  2. Yosys tổng hợp sh_ba ra bao nhiêu flip-flop?

Tóm tắt

  • <= giữ đúng số tầng thanh ghi; = trong logic tuần tự có thể xoá tầng.
  • Testbench cũng cần tránh đổi tín hiệu đúng tại cạnh mà DUT lấy mẫu.

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

  1. ChipVerify — Verilog tutorial, §7 Assignments / Blocking & Non-Blocking. Trích dẫn; nguồn mới xem mục lục. chỉ dùng làm danh mục chủ đề
  2. ChipVerify — Verilog tutorial, §7 Assignments. Trích dẫn; nguồn mới xem mục lục. danh mục chủ đề