Mở đầu: Con chip sai ở 200 MHz — và vẫn sai kể cả khi hạ xuống 20 MHz
Mọi kỹ sư đều hiểu chuyện này: đẩy tần số clock lên quá cao, dữ liệu không kịp chạy hết đường logic tổ hợp trước cạnh clock kế tiếp, mạch tính sai — hạ tần số xuống là hết. Nhưng có một họ lỗi timing thứ hai ngược đời hơn nhiều: mạch sai ở 200 MHz, bạn hạ xuống 100 MHz — vẫn sai. Hạ tiếp xuống 20 MHz — vẫn sai y nguyên. Đổi board, đổi nhiệt độ, lỗi lúc có lúc không. Nếu chưa học bài này, phản xạ "giảm tần số cho an toàn" sẽ khiến bạn mắc kẹt hàng tuần với một con bug mà về mặt toán học không phụ thuộc gì vào tần số.
Bài này trang bị bộ công cụ suy luận đứng sau mọi con số "chip chạy tối đa X MHz" trên datasheet: 4 tham số timing của một đường dữ liệu giữa 2 flip-flop, công thức tính chu kỳ clock tối thiểu, khái niệm critical path và slack mà công cụ STA báo cáo, vì sao lỗi hold "miễn nhiễm" với tần số, và pipeline — kỹ thuật cắt đôi critical path để tăng tần số, trả giá bằng độ trễ. Cuối bài là STA Workbench: kéo slider độ trễ từng loại cổng trên chính bộ cộng ripple-carry của Bài 6 và xem critical path đổi màu đỏ theo thời gian thực.
rca4 đã xây ở đó, và mô hình "số tầng cổng" của Bài 6 chính là phiên bản đơn giản hoá của
STA; Bài 3 (SystemVerilog tuần tự) — mọi đường timing đều bắt đầu và
kết thúc ở flip-flop always_ff;
Series Điện Tử Bài 13 (Mạch tuần tự & setup/hold)
— bài này giả định bạn đã biết setup/hold time là gì ở mức vật lý.
Tài nguyên ngoài: HDLBits, ChipVerify, EDA Playground.
1. Bốn tham số timing của một đường dữ liệu
Trong mạch đồng bộ, mọi đường dữ liệu (timing path) đều có cùng một cấu trúc: xuất phát từ một flip-flop nguồn, chạy qua một đám mây logic tổ hợp, và kết thúc ở chân D của một flip-flop đích. Toàn bộ phân tích timing xoay quanh 4 con số trên đường đi đó:
- $t_{clk \to q}$ (clock-to-Q): thời gian từ cạnh clock đến khi ngõ ra Q của flip-flop nguồn thực sự đổi giá trị — flip-flop không phản ứng tức thời.
- $t_{comb}$ (propagation delay tổ hợp): thời gian tín hiệu lan truyền qua đám mây cổng logic giữa 2 flip-flop — mỗi cổng XOR/AND/OR góp thêm một phần trễ.
- $t_{setup}$: dữ liệu phải ổn định TRƯỚC cạnh clock của flip-flop đích ít nhất khoảng này, nếu không flip-flop có thể lấy mẫu sai (đã học vì sao ở Series Điện Tử Bài 13).
- $t_{hold}$: dữ liệu phải giữ nguyên SAU cạnh clock thêm ít nhất khoảng này — cạnh clock không "chụp ảnh" trong 0 giây, nó cần dữ liệu đứng yên quanh thời điểm lấy mẫu.
// Đường timing tối giản: FF nguồn → 2 cổng logic → FF đích.
// Chạy được trên VeriLite (demo Bài 3) — nhưng lưu ý: VeriLite mô phỏng
// CHỨC NĂNG (0/1 đúng hay sai), hoàn toàn không mô phỏng ĐỘ TRỄ thật.
// Timing là chuyện của STA, không phải của RTL simulator — đó là lý do
// bài này cần một workbench riêng thay vì tái dùng waveform viewer.
module two_ff_path (
input logic clk,
input logic d_in,
output logic q_out
);
logic q1; // ngõ ra FF nguồn
logic comb; // ngõ ra đám mây tổ hợp
assign comb = ~q1 ^ d_in; // 2 tầng cổng: NOT rồi XOR
always_ff @(posedge clk) begin
q1 <= d_in; // FF nguồn
q_out <= comb; // FF đích — chân D phải thoả setup/hold quanh cạnh clock
end
endmodule
Biểu đồ timing dưới đây đặt cả 4 tham số lên cùng một trục thời gian, giữa 2 cạnh clock liên tiếp — đọc từ trái sang phải chính là hành trình của 1 bit dữ liệu:
Cạnh clock 1 "bắn" dữ liệu ra khỏi FF nguồn (mất $t_{clk \to q}$), dữ liệu chạy qua logic tổ hợp (mất $t_{comb}$) rồi phải đến chân D của FF đích sớm hơn cạnh clock 2 ít nhất $t_{setup}$ — và đứng yên thêm $t_{hold}$ sau cạnh đó.
2. Chu kỳ tối thiểu và $f_{max}$: một bất đẳng thức quyết định tất cả
Ghép hành trình trên thành điều kiện toán học: dữ liệu xuất phát tại cạnh 1, đi hết $t_{clk \to q} + t_{comb}$, và phải cán đích trước thời điểm $T_{clk} - t_{setup}$ (cạnh 2 trừ đi khoảng setup). Đường chậm nhất trong toàn mạch (ký hiệu $t_{comb,max}$) quyết định chu kỳ clock ngắn nhất mà mạch còn chạy đúng:
$$T_{min} = t_{clk \to q} + t_{comb,max} + t_{setup}$$
Nghĩa là: chu kỳ clock không được ngắn hơn tổng "thời gian FF nguồn phản ứng + thời gian đường logic chậm nhất + khoảng an toàn setup của FF đích". Từ đó suy ra tần số tối đa:
$$f_{max} = \frac{1}{T_{min}} = \frac{1}{t_{clk \to q} + t_{comb,max} + t_{setup}}$$
Con số $f_{max}$ trên datasheet của mọi con chip chính là nghịch đảo của tổng 3 độ trễ này trên đường chậm nhất. Thử với số cụ thể: $t_{clk \to q} = 0{,}6$ ns, đường tổ hợp chậm nhất $t_{comb,max} = 3{,}0$ ns, $t_{setup} = 0{,}4$ ns:
$$T_{min} = 0{,}6 + 3{,}0 + 0{,}4 = 4{,}0 \text{ ns}$$
$$f_{max} = \frac{1}{4{,}0 \text{ ns}} = 250 \text{ MHz}$$
Tức là mạch này chạy đúng ở mọi tần số từ 0 đến 250 MHz — đẩy lên 260 MHz, chu kỳ chỉ còn ~3,85 ns, dữ liệu trên đường chậm nhất đến trễ 0,15 ns so với hạn chót setup, và flip-flop đích bắt đầu lấy mẫu giá trị rác.
3. Critical path & slack: STA quét mọi đường mà không cần mô phỏng
Critical path (đường tới hạn) là đường có tổng độ trễ lớn nhất trong mạch — chính là $t_{comb,max}$ trong công thức trên. Điều thú vị: STA tìm ra nó mà không chạy mô phỏng nào cả ("tĩnh" nghĩa là vậy). Công cụ STA xem mạch như một đồ thị có hướng: mỗi cổng là một nút mang trọng số độ trễ, và bài toán trở thành tìm đường đi dài nhất giữa 2 flip-flop — cùng họ với các bài toán đường đi trên đồ thị ở Series DSA (Dijkstra/A*), chỉ khác là tìm max thay vì min. Nhờ vậy STA phủ được 100% mọi đường — kể cả những đường mà cả nghìn tỉ vector mô phỏng chưa chắc kích hoạt tới.
Áp dụng lên bộ cộng rca4 quen thuộc của Bài 6 — giờ không đếm
"số tầng cổng" trừu tượng nữa mà gán độ trễ nanô-giây thật cho từng loại cổng, đúng như STA đọc từ thư
viện standard cell (liberty timing) mà Bài 6 đã hứa hẹn:
// Trích từ Bài 6 — mỗi dòng carry phải CHỜ dòng trước nó:
assign c1 = (a0 & b0) | (cin & (a0 ^ b0)); // chờ cin
assign c2 = (a1 & b1) | (c1 & (a1 ^ b1)); // chờ c1
assign c3 = (a2 & b2) | (c2 & (a2 ^ b2)); // chờ c2
assign cout = (a3 & b3) | (c3 & (a3 ^ b3)); // chờ c3 — cuối chuỗi gợn sóng
assign s3 = a3 ^ b3 ^ c3; // sum bit cuối cũng chờ c3
Với bộ độ trễ mẫu (XOR 0,9 ns · AND 0,5 ns · OR 0,5 ns · $t_{clk \to q}$ 0,6 ns), thời điểm tín hiệu đến nơi (arrival time, tính từ cạnh clock) của từng ngõ ra như sau — các con số này do đúng mô hình trong STA Workbench ở Mục 6 tính ra, bạn có thể tự kiểm chứng:
| Ngõ ra | Đường đi | Arrival (ns) |
|---|---|---|
s0 |
reg → XOR → XOR | 2,4 |
s1 |
reg → (chuỗi carry c1) → XOR | 3,4 |
s2 |
reg → (c1 → c2) → XOR | 4,4 |
s3 |
reg → (c1 → c2 → c3) → XOR | 5,4 |
cout ← critical path |
reg → c1 → c2 → c3 → c4 (trọn chuỗi gợn sóng) | 5,5 |
Slack của một đường là khoảng "dư dả" so với hạn chót: chu kỳ clock trừ đi tổng thời gian đường đó cần.
$$slack = T_{clk} - \left( t_{arrival} + t_{setup} \right)$$
Slack dương = đường đó về đích sớm, còn dư thời gian; slack âm = vi phạm
timing, mạch sẽ tính sai ở tần số này. Ví dụ với $T_{clk} = 6{,}5$ ns: đường
cout có slack $= 6{,}5 - (5{,}5 + 0{,}4) = +0{,}6$ ns — đạt. Siết xuống
$T_{clk} = 5{,}0$ ns: slack $= 5{,}0 - 5{,}9 = -0{,}9$ ns — vi phạm, và báo cáo
STA sẽ in đúng dòng đường cout này kèm con số âm đó để bạn biết phải sửa ở đâu. Toàn mạch đạt
timing khi và chỉ khi đường xấu nhất (worst slack) không âm.
4. Hold violation: vì sao giảm tần số không cứu được
Điều kiện setup ở Mục 2 so dữ liệu với cạnh clock kế tiếp — nên nới $T_{clk}$ ra là thoát. Điều kiện hold hoàn toàn khác: nó so dữ liệu mới (vừa được cạnh clock hiện tại bắn ra từ FF nguồn) với chính cạnh clock đó tại FF đích. Dữ liệu mới chạy qua đường ngắn nhất không được đến đích quá sớm, nếu không nó "đè" lên giá trị cũ ngay trong lúc FF đích còn đang lấy mẫu:
$$t_{clk \to q} + t_{comb,min} \ge t_{hold}$$
Hãy nhìn kỹ bất đẳng thức này: không có $T_{clk}$ ở bất kỳ đâu. Cả 3 đại lượng đều là hằng số vật lý của mạch — giảm tần số, tăng tần số, chờ chip nguội đi... đều không đổi được vế nào. Đây chính là lời giải cho bí ẩn ở phần Mở đầu.
Phản xạ này chỉ đúng với lỗi setup. Ví dụ một đường bypass gần như nối thẳng 2 flip-flop: $t_{clk \to q, min} = 0{,}25$ ns, $t_{comb,min} \approx 0$, trong khi $t_{hold} = 0{,}30$ ns. Kiểm tra: $0{,}25 + 0 = 0{,}25 < 0{,}30$ — vi phạm hold, thiếu 0,05 ns, ở mọi tần số từ 1 Hz đến 1 GHz.
// ❌ SAI: hạ tần số clock từ 200 MHz xuống 20 MHz
// T_clk tăng 10 lần nhưng KHÔNG xuất hiện trong bất đẳng thức hold
// → 0,25 ns vẫn < 0,30 ns → vẫn vi phạm y nguyên.
//
// ✅ ĐÚNG: chèn thêm độ trễ vào đường NGẮN đó (công cụ Place & Route
// tự làm khi được báo vi phạm hold — chỉ minh hoạ khái niệm, đây
// không phải cú pháp SystemVerilog và ngoài subset VeriLite):
//
// FF1.Q ──▶ BUF(0,15 ns) ──▶ BUF(0,15 ns) ──▶ FF2.D
//
// t_clk→q + t_comb,min = 0,25 + 0,30 = 0,55 ns ≥ 0,30 ns → đạt.
// Chuỗi buffer làm đường ngắn CHẬM ĐI — vô hại với setup miễn là
// đường đó còn cách xa critical path.
Ghi nhớ thực chiến: lỗi setup — sửa được bằng hạ tần số (tạm thời) hoặc rút ngắn đường dài (pipeline, Mục 5); lỗi hold — bắt buộc sửa mạch (chèn delay/buffer vào đường ngắn), và phải sửa trước khi tape-out vì sau khi sản xuất thì không còn cách nào cứu.
5. Pipeline: cắt critical path, đổi độ trễ lấy tần số
Quay lại bộ cộng rca4: critical path 5,5 ns giới hạn $f_{max}$ ở
$1/(5{,}5 + 0{,}4) \approx 169{,}5$ MHz. Muốn nhanh hơn mà vẫn giữ kiến trúc ripple-carry?
Chặt đôi chuỗi carry: đặt một dàn thanh ghi ngay sau FA1 — nửa đầu (FA0, FA1) tính ở chu
kỳ 1, kết quả dở dang (s0, s1, c2 và các bit cao chưa dùng) được
chốt lại, nửa sau (FA2, FA3) tính ở chu kỳ 2:
// Bộ cộng 4-bit pipeline 2 tầng — chạy được trên VeriLite.
// Đã kiểm chứng đủ 512 tổ hợp a/b/cin + luồng dữ liệu mới mỗi chu kỳ:
// độ trễ đúng 2 cạnh clock, thông lượng 1 kết quả/chu kỳ.
module rca4_pipe2 (
input logic clk,
input logic a0, a1, a2, a3,
input logic b0, b1, b2, b3,
input logic cin,
output logic s0, s1, s2, s3,
output logic cout
);
// ---- Giai đoạn 1 (tổ hợp): FA0 + FA1 — nửa đầu chuỗi carry ----
logic s0_c, s1_c, c1, c2_c;
assign s0_c = a0 ^ b0 ^ cin;
assign c1 = (a0 & b0) | (cin & (a0 ^ b0));
assign s1_c = a1 ^ b1 ^ c1;
assign c2_c = (a1 & b1) | (c1 & (a1 ^ b1));
// ---- Dàn thanh ghi pipeline: chốt kết quả dở dang + input nửa sau ----
logic s0_r, s1_r, c2_r, a2_r, b2_r, a3_r, b3_r;
always_ff @(posedge clk) begin
s0_r <= s0_c;
s1_r <= s1_c;
c2_r <= c2_c;
a2_r <= a2; // bit cao phải "đi cùng chuyến" để không lệch nhịp
b2_r <= b2;
a3_r <= a3;
b3_r <= b3;
end
// ---- Giai đoạn 2 (tổ hợp): FA2 + FA3 — nửa sau chuỗi carry ----
logic c3, s2_c, s3_c, cout_c;
assign s2_c = a2_r ^ b2_r ^ c2_r;
assign c3 = (a2_r & b2_r) | (c2_r & (a2_r ^ b2_r));
assign s3_c = a3_r ^ b3_r ^ c3;
assign cout_c = (a3_r & b3_r) | (c3 & (a3_r ^ b3_r));
// ---- Thanh ghi kết quả ----
always_ff @(posedge clk) begin
s0 <= s0_r; // nửa thấp đã xong từ giai đoạn 1, chỉ "chờ cùng chuyến"
s1 <= s1_r;
s2 <= s2_c;
s3 <= s3_c;
cout <= cout_c;
end
endmodule
Giờ mỗi giai đoạn chỉ còn nửa chuỗi carry: đường xấu nhất mỗi tầng là 3,5 ns (thay vì 5,5 ns), nên $T_{min} = 3{,}5 + 0{,}4 = 3{,}9$ ns. So sánh trước–sau với cùng bộ độ trễ:
| rca4 (1 tầng) | rca4_pipe2 (2 tầng) | |
|---|---|---|
| Đường xấu nhất (arrival) | 5,5 ns | 3,5 ns / tầng |
| $f_{max}$ | 169,5 MHz | 256,4 MHz (+51%) |
| Latency (1 phép cộng đơn lẻ) | 1 chu kỳ = 5,9 ns | 2 chu kỳ = 7,8 ns (chậm hơn!) |
| Throughput (luồng dữ liệu liên tục) | 1 kết quả / 5,9 ns | 1 kết quả / 3,9 ns |
| Chi phí | — | +7 flip-flop (diện tích, công suất) |
Bảng này gói trọn triết lý pipeline: một phép cộng đơn lẻ thực ra CHẬM ĐI (7,8 ns so với 5,9 ns — thêm một lần $t_{clk \to q}$ + setup ở giữa), nhưng khi dữ liệu chảy liên tục, cả 2 tầng làm việc song song trên 2 phép cộng khác nhau, cho ra 1 kết quả mỗi 3,9 ns. Đây chính là lý do CPU hiện đại có pipeline 14–20 tầng: đổi latency lấy tần số và thông lượng — và cũng là nội dung phần mở rộng của dự án CPU RISC-V ở Bài 13–14 sắp tới.
6. Thực hành: STA Workbench
Toàn bộ con số ở Mục 3 và 5 đều "sống" trong workbench này. Kéo slider độ trễ từng loại cổng — bảng
arrival/slack và $f_{max}$ cập nhật tức thì, critical path tô đỏ tự động trên sơ đồ. Hai thí
nghiệm nên thử: (1) kéo $T_{clk}$ nhỏ dần và quan sát slack chuyển âm từng
đường một — nhưng dòng "kiểm tra hold" bên dưới đứng yên tuyệt đối, đúng như Mục 4;
(2) kéo độ trễ XOR lên cao nhất, AND/OR xuống thấp nhất — critical path
nhảy từ cout sang s3 (chuỗi carry rẻ đi, cổng XOR cuối của sum
đắt lên), minh hoạ vì sao STA phải quét mọi đường thay vì tin vào trực giác "chuỗi carry luôn chậm nhất".
Đường đỏ trên sơ đồ = critical path hiện tại. Ô slack đỏ = đường vi phạm timing ở $T_{clk}$ đang chọn. Dòng kiểm tra hold không bao giờ đổi khi bạn kéo $T_{clk}$ — đó chính là bài học của Mục 4.
// Quy tắc STA duy nhất cần nhớ: arrival của 1 cổng =
// max(arrival mọi đầu vào) + độ trễ của chính cổng đó.
// Với chuỗi carry của rca4 (c_next = g | (p & c)):
let A = tcq; // carry-in xuất phát từ thanh ghi
for (let i = 0; i < 4; i++) {
const p = tcq + txor; // p_i = a^b — tính song song mọi bit
const g = tcq + tand; // g_i = a&b
arrival['s' + i] = Math.max(p, A) + txor; // sum: thêm 1 XOR
A = Math.max(g, Math.max(p, A) + tand) + tor; // carry kế tiếp
}
arrival.cout = A;
// T_min = max(mọi arrival) + t_setup; f_max = 1 / T_min
// slack(đường) = T_clk − (arrival + t_setup) → âm là vi phạm
Tóm lược
Bài này biến trực giác "mạch nhanh hay chậm" thành các bất đẳng thức kiểm chứng được:
- ✅ Mọi đường dữ liệu đồng bộ được mô tả bởi 4 tham số: $t_{clk \to q}$, $t_{comb}$, $t_{setup}$, $t_{hold}$.
- ✅ $f_{max} = 1/(t_{clk \to q} + t_{comb,max} + t_{setup})$ — đường chậm nhất (critical path) quyết định tần số cả chip.
- ✅ STA tìm critical path bằng phân tích đồ thị (đường đi dài nhất), phủ 100% mọi đường không cần mô phỏng; slack âm = vi phạm.
- ✅ Hold violation không phụ thuộc chu kỳ clock — giảm tần số vô dụng, phải chèn delay vào đường ngắn.
- ✅ Pipeline cắt critical path: $f_{max}$ và throughput tăng, đổi lấy latency và thêm flip-flop.
Trắc nghiệm ôn tập
Câu 1
Vì sao $t_{hold}$ không xuất hiện trong công thức $f_{max} = 1/(t_{clk \to q} + t_{comb,max} + t_{setup})$?
Câu 2
Báo cáo STA cho biết đường xấu nhất có slack = −0,9 ns tại $T_{clk}$ = 5,0 ns. Điều đó nghĩa là gì?
Câu 3
Một mạch bị vi phạm hold (đường ngắn nhất: $t_{clk \to q} + t_{comb,min} = 0{,}25$ ns < $t_{hold} = 0{,}30$ ns). Cách sửa nào có tác dụng?
Câu 4
Pipeline hoá bộ cộng rca4 thành 2 tầng (như rca4_pipe2) làm thay đổi các chỉ số thế nào?
Tải file code thực hành minh họa bài học
File SystemVerilog gồm đường timing 2-FF tối giản, bộ cộng rca4 (từ Bài 6) và bản pipeline
2 tầng rca4_pipe2, kèm testbench đối chiếu kết quả 2 phiên bản trên cùng luồng dữ liệu và
kiểm chứng độ trễ 2 chu kỳ:
Bình luận