• Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

  • Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

  • Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

Hiển thị các bài đăng có nhãn background. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn background. Hiển thị tất cả bài đăng

Chủ Nhật, 2 tháng 2, 2020

[Multi-clock Design] Bài 3 - Kỹ thuật đồng bộ tín hiệu nhiều bit

Bài viết này trình bày về các phương pháp và kỹ thuật phổ biến dùng để đồng bộ một tín hiệu nhiều bit, 2 bit trở lên, khi tín hiệu này được lan truyền giữa hai miền clock bất đồng bộ (asynchronous clock) trong các chip sử dụng nhiều nguồn clock khác nhau.
Một số từ khóa và từ viết tắt:
  • Trạng thái bất ổn định (metastability)
  • Mạch đồng bộ (synchronizer)
  • FF (Flip-Flop)
Các bài viết liên quan:

1) Vấn đề đồng bộ tín hiệu nhiều bit
Đối với tín hiệu 1 bit, việc đồng bộ có thể đơn giản là chỉ cần 2 hoặc nhiều Flip-Flop (FF) mắc nối tiếp. Nếu trạng thái bất ổn định (metastability) xảy ra và ổn định trước khi lan truyền đến FF cuối cùng trong mạch đồng bộ thì giá trị ngõ ra của mạch đồng bộ sẽ là giá trị trước đó hoặc giá trị mới được mong muốn. Điểm này rất quan trọng vì nó giúp miền nhận không xử lý sai khi dùng tín hiệu ngõ ra mạch đồng bộ làm tín hiệu điều khiển các logic khác.
Hình 1: Hiện tượng bất ổn định xảy ra trên tín hiệu 1 bit
Trong hình minh họa trên, khi hiện tượng bất ổn định xảy ra ở FF đầu tiên, pre_out sẽ ổn định về giá trị 0 (giá trị trước đó) hoặc 1 (giá trị mới). Điều này làm cho ngõ ra mạch đồng bộ sync_out chỉ có thể là 0 (giá trị trước đó) hoặc 1 (giá trị mới) ở chu kỳ tiếp theo.
  • Nếu sync_out=0 (giá trị cũ) thì nó sẽ không làm thay đổi trạng thái của logic mà nó sẽ điều khiển. Khi giá trị ngõ vào async_in vẫn duy trì mức tích cực thì sync_out=1 và lái logic mà nó điều khiển đến trạng thái mới như mong muốn
  • Nếu sync_out=1 (giá trị mới) thì nó lái logic đến trạng thái mới như mong muốn.
Do hiện tượng bất ổn định, một tín hiệu nhiều bit (2 bit trở lên) như bus dữ liệu, bus địa chỉ, giá trị trạng thái, … thường không thể chỉ được đồng bộ chỉ bằng 2 hoặc nhiều FF như đối với tín hiệu 1 bit. Chú ý, "thường không thể" nghĩa là một tín hiệu nhiều bit vẫn có thể chỉ cần đồng bộ bằng 2 hoặc nhiều FF nối tiếp trong một số trường hợp đặc biệt. Nguyên nhân, nếu hai hoặc nhiều bit thay đổi giá trị và cùng gây ra hiện tượng bất ổn định thì một giá trị không mong muốn có thể được sinh ra trên ngõ ra của mạch đồng bộ.
Hình 2: Ảnh hưởng của hiện tượng bất ổn định trên tín hiệu nhiều bit
Trong hình minh họa trên đây, khi xảy ra hiện tượng bất ổn định thì giá trị sync_out có thể là một trong các tổ hợp 00/01/10/11. Nếu tổ hợp xảy ra là 00 (giá trị cũ) hoặc 11 (giá trị mới) thì miền nhận có thể xử lý đúng như mong muốn. Nhưng, nếu một trong hai tổ hợp 01 và 10 xuất hiện thì miền nhận có thể xử lý sai vì đây là hai trạng thái không mong muốn. Vì vậy, đối với tín hiệu nhiều bit, một cơ chế hoặc phương pháp đồng bộ cần được áp dụng để tránh nhận các giá trị không mong muốn.
Để thực hiện cơ chế đồng bộ cho một tín hiệu nhiều bit, chúng ta cần tập trung vào 2 điểm sau đây:
  • Việc sinh ra các giá trị không mong muốn là do 2 hoặc nhiều bit của tín hiệu cùng thay đổi mức logic. Điều này làm cho hiện tượng bất ổn định có thể xảy ra cùng lúc trên các bit được thay đổi mức logic. Như vậy, một tín hiệu nhiều bit có thể chỉ cần đồng bộ bởi 2 hoặc nhiều FF mắc nối tiếp nếu chỉ 1 bit thay đổi tại một thời điểm trong suốt quá trình hoạt động. Nghĩa là, giá trị trên tín hiệu không bao giờ thay đổi giá trị nhiều bit cùng một lúc. Trong trường hợp này, nếu hiện tượng bất ổn định xảy ra thì giá trị của tín hiệu này sẽ chỉ có thể là giá trị trước đó hoặc giá trị mới như mong muốn chứ không xuất hiện tổ hợp giá trị không mong muốn.
  • Khi hiện tượng bất ổn định xảy ra, trong lần cập nhật giá trị mới đầu tiên, tại thời điểm T2 của hình 2, giá trị ngõ ra của mạch đồng bộ là một giá trị ngẫu nhiên. Để đảm bảo ngõ ra cập nhật được giá trị logic mới thì ngõ vào mạch đồng bộ phải duy trì ổn định giá trị mới trong một khoảng thời gian đảm bảo cho miền clock nhận bắt được giá trị đúng. Khoảng thời gian này phải lớn hơn (chu kỳ của clock nhận + thời gian setup của FF + thời gian hold của FF). Theo hình 2, async_in có thể được giữ 2 chu kỳ xung clock rclk để đảm bảo một giá trị đúng có thể được bắt bởi cạnh lên T2.
2) Ví dụ về đồng bộ sai tín hiệu nhiều bit
Một giá trị của máy trạng thái (FSM) cần được truyền từ xung clock tclk sang miền xung clock rclk. Giá trị này gồm 2 bit tên bus_state[1:0] quy định các trạng thái như sau:
IDLE = 2’b00;
ACTIVATE = 2b01;
RUN = 2’b10;
DEACTIVATE = 2’b11;
Hình 3: Ví dụ sai về việc đồng bộ một giá trị trạng thái nhiều bit
Trong hình vẽ trên, trạng thái được chuyển đổi lặp lại theo thứ tự IDLE->ACTIVATE->RUN->ACTIVATE. Giả sử bus_state[1:0] chỉ được đồng bộ bằng 2 FF tại miền xung clock rclk:
  • Nếu trạng thái chuyển từ IDLE (2’b00) -> ACTIVATE (2’b01) hoặc chuyển từ RUN (2’b10) -> DEACTIVATE (2’b11) thì miền clock nhận rclk sẽ phát hiện đúng trạng thái cho dù hiện tượng bất ổn định xảy ra vì chỉ một bit thay đổi giá trị.
  • Nếu trạng thái chuyển từ ACTIVATE (2’b01) -> RUN (2’b10) hoặc chuyển từ DEACTIVATE (2’b11) -> IDLE (2’b00) thì miền clock nhận rclk có thể nhận được một trạng thái sai nếu hiện tượng bất ổn định xảy ra. Ví dụ, ACTIVATE có thể chuyển sang trạng thái IDLE (2'b00) hoặc DEACTIVATE (2'b11) trước khi chuyển sang trạng thái RUN.
Một ví dụ khác, giá trị của một bộ đếm lên nhị phân truyền từ miền clock tclk sang miền clock rclk. Nếu giá trị bộ đếm này chỉ đồng bộ bằng các FF mắc nối tiếp, ví dụ như 2 FF, thì giá trị nhận được tại miền clock rclk có thể sẽ không tuần tự, tăng đều một đơn vị như bên miền clock tclk khi hiện tượng bất ổn định xảy ra.
Hình 4: Ví dụ sai về việc đồng bộ một bộ đếm nhị phân nhiều bit
Trong hình minh họa trên đây, một bộ đếm lên nhị phân 4 bit, bcounter[3:0],
  • Miền tclk: 0000 (0) -> 0001 (1) -> 0010 (2) -> 0011 (3) -> 0100 (4) -> …
  • Miền rclk: 0000 (0) -> 0001 (1) -> (0, 3) -> 0010 (2) -> 0011 (3) -> (0, 1, 2, 5, 6, 7) -> 0100 (4) -> …
Trong ví dụ trên đây khi bộ đếm tăng giá trị từ 1 lên 2, hai bit cuối thay đổi trạng thái logic. Tại miền clock rclk, nếu xảy ra hiện tượng bất ổn định trên 2 bit, giá trị bộ đếm nhận được có thể là 0 hoặc 3 trước khi đạt đến giá trị 2. Tương tự, tại thời điểm giá trị bộ đếm tang từ 3 lên 4, 3 bit cuối thay đổi mức logic. Nếu hiện tượng metastable xảy ra trên cả 3 bit, giá trị bộ đếm nhận được trên miền rclk có thể là 0, 1, 2, 5, 6 hoặc 7 trước khi đạt đến giá trị đúng là 4.
3) Các phương pháp đồng bộ tín hiệu nhiều bit
Việc đồng bộ tín hiệu nhiều bit cần giải quyết 2 vấn đề:
  • Hạn chế sự lan truyền trạng thái bất ổn định của tín hiệu đến các logic bên trong miền clock nhận
  • Lấy mẫu đúng giá trị mong muốn hoặc miền clock nhận không xử lý (hoạt động) sai khi hiện tượng bất ổn định xảy ra và triệt tiêu trên những tầng đầu tiên của mạch đồng bộ nhiều FF mắc nói tiếp.
Tùy vào trường hợp cụ thể, một trong các phương pháp đồng bộ sau đây có thể được áp dụng để đồng bộ tín hiệu nhiều bit:
  • Sử dụng logic kiểm tra giá trị tín hiệu nhận được để đảm bảo nhận được giá trị đúng
  • Sử dụng tín hiệu cho phép (enable) để xác định thời điểm giá trị hợp lệ
  • Sử dụng cơ chế chuyển đổi giá trị thay đổi nhiều bit thành giá trị chỉ thay đổi 1 bit
  • Sử dụng giao thức bắt tay giữa hai miền clock để nhận biết khi nào giá trị được truyền và khi nào giá trị đã được nhận. Có hai loại giao thức bắt tay phổ biến là:
    • Bắt tay 4 pha (4-phase handshake)
    • Bắt tay 2 pha (2-phase handshake)
  • Sử dụng bộ nhớ đệm giữa hai miền như FIFO, RAM, ...
Xin nhấn mạnh rằng, việc áp dụng phương pháp nào để đồng bộ phải căn cứ trên yêu cầu thực tế vì mỗi phương pháp sẽ có ưu điểm và nhược điểm riêng. Các mục tiếp theo sẽ mô tả tổng quát các phương pháp trên.
4) Phương pháp kiểm tra sự ổn định của giá trị nhận được
4.1) Mô tả nguyên lý
Như đã trình bày ở các mục trên, khi hiện tượng bất ổn định xảy ra ở các tầng FF trước tầng FF cuối cùng, giá trị nhận được có thể sai. Giá trị này chỉ tồn tại một chu kỳ xung clock miền nhận, rclk, nếu ngõ vào từ miền clock truyền, tclk, tiếp tục giữ ổn định giá trị trong nhiều chu kỳ xung clock rclk tiếp theo.
Hình 5: Mạch nguyên lý của phương pháp kiểm tra sự ổn định của giá trị nhận
Trong hình minh họa trên, một tầng FF được sử dụng ngay sau mạch đồng bộ để lưu lại giá trị ngõ ra của mạch đồng bộ, sync_out, theo từng chu kỳ xung clock rclk. Giá trị của tầng FF này, sync_tmp, sẽ được so sánh với ngõ ra của mạch đồng bộ, sync_out. Nếu hai giá trị này bằng nhau thì đây là giá trị ổn định được truyền từ miền clock tclk. Ngõ ra bộ so sánh, data_valid, sẽ tích cực để báo hiệu một giá trị hợp lệ cần được nhận. Trong ví dụ minh họa, khi data_valid tích cực, thanh ghi rec_data sẽ cập nhật giá trị mới. Giá trị mới có thể lấy từ sync_tmp hoặc sync_out vì lúc này, giá trị của hai tín hiệu này bằng nhau và bằng giá trị ổn định được truyền từ miền tclk.
4.2) Điều kiện sử dụng
Điều kiện sử dụng của mạch này là tín hiệu ngõ vào, async_in, phải duy trì ổn định giá trị mới đủ để rclk bắt đúng giá trị này từ 2 lần trở lên.
Chi tiết hơn, chúng ta hãy xem xét chi tiết sự tích cực của tín hiệu data_valid trong trường hợp xấu nhất để hiểu rõ hoạt động của cơ chế so sánh này.
Trường hợp xấu nhất là trường hợp xảy ra vi phạm timing khi ngõ thay đổi giá trị. 
Hình 6: Một waveform của ví dụ hình 5
Trong hình minh họa trên đây, async_in được duy trì trong 3 chu kỳ xung clock rclk. Điều này đảm bảo rclk chắc chắn bắt được giá trị đúng tại cạnh lên ở T2 và T3. Lưu ý, async_in được lái theo clock tclk nhưng tclk không được thể hiện ở đây.
Trường hợp xấu nhất, thời điểm T1 và T4 đều vi phạm timing, giá trị sync_out chỉ chắc chắn có giá trị mong muốn là 2'b11 trong 2 chu kỳ từ T3 đến T4 và từ T4 đến T5. Điều này ứng với việc data_valid chỉ đảm bảo chắc chắn tích cực trong chu kỳ từ T4 đến T5, lúc data_tmp=data_out=2'b11.
Nếu vi phạm timing tại T1 làm giá trị pre_out ổn định về 2'b11 thì data_valid sẽ tích cực trong chu kỳ từ T3 đến T4.
Nếu vi phạm timing tại T4 làm giá trị pre_out ổn định về 2'b11 thì data_valid sẽ tích cực trong chu kỳ từ T5 đến T6.
Như vậy độ rộng tối thiểu là data_valid là 1 chu kỳ rclk và độ rộng tối đa của data_valid là 3 chu kỳ rclk.
4.3) Ưu điểm và chú ý
Mục này nêu ra các ưu điểm và chú ý khi dùng phương pháp này. "chú ý" có thể là nhược điểm của phương pháp này hoặc không.
Ưu điểm:
  • Cơ chế đồng bộ đơn giản
  • Không cần tạo thêm tín hiệu giao tiếp mới giữa hai miền lock truyền và nhận
Chú ý:
  • Phải tuân thủ ràng buộc về độ rộng xung của tín hiệu, một giá trị mới phải duy trì trong khoảng thời gian đủ để miền clock nhận chắc chắn bắt đúng từ 2 lần trở lên. Điều này làm cho miền clock truyền, tclk, có thể phải thêm logic để duy trì độ rộng xung của các tín hiệu như yêu cầu.
  • Số lượng FF của mạch đồng bộ càng lớn khi số lượng bit tín hiệu càng nhiều. Điểm này ảnh hưởng đến tài nguyên và công suất tiêu thụ.
  • Tài nguyên bộ so sánh giá trị lớn khi số lượng bit càng nhiều. Điểm này ảnh hưởng lớn đến độ trễ trên logic mạch tổ hợp.
  • Thời gian tối thiểu để miền nhận có thể nhận biết đúng giá trị mới là 4 chu kỳ rclk, từ lúc ngõ vào thay đổi giá trị ở T1 đến lúc nhận biết được data_valid tích cực ở T5.
  • Không sử dụng cho trường hợp một giá trị giống nhau được dùng nhiều lần liên tiếp. Nếu miền clock tclk truyền liên tiếp một giá trị nhiều lần, ví dụ như truyền 10 lần giá trị 2'b00, và các giá trị này là phân biệt nhau thì phương pháp này không thể nhận biết được số lần truyền nhận vì nó chỉ giữ tích cực tín hiệu data_valid liên tiếp nhiều chu kỳ trong trường hợp này.
5) Phương pháp dùng tín hiệu cho phép
5.1) Mô tả nguyên lý
Thay vì để miền nhận tự kiểm tra giá trị hợp lệ. Miền truyền tclk sẽ tạo thêm một tín hiệu 1 bit để báo hiệu thời điểm giá trị truyền hợp lệ. Trong cơ chế này, chỉ tín hiệu cho phép là cần được đồng bộ qua mạch nhiều FF mắc nối tiếp.
Hình 7: Mạch nguyên lý của phương pháp đồng bộ dùng tín hiệu cho phép
Trong mạch nguyên lý trên, data_valid được đồng bộ bằng 2 FF để tạo thành data_en. Sau đó, data_en được dùng để điều khiển thời điểm cập nhật giá trị mới từ async_in đến rec_data.
5.2) Điều kiện sử dụng
Điều kiện sử dụng của phương pháp này là:
  • Tín hiệu cho phép data_valid phải ổn định đủ để miền clock rclk chắc chắn bắt được đúng giá trị tích cực. Thời gian này phải lớn hơn "một chu kỳ rclk + thời gian setup của FF + thời gian hold của FF"
  • Tín hiệu ngõ vào async_in phải duy trì ổn định giá trị cần truyền trong mọi trường hợp data_en tích cực.
Xem xét waveform sau đây để xác định mối liên hệ giữa data_valid và thời gian duy trì giá trị cần truyền trên async_in.
Hình 8: Một waveform của ví dụ hình 7
Trong hình minh họa trên đây, data_valid được duy trì trong 2 chu kỳ xung clock rclk. Điều này đảm bảo rclk chắc chắn bắt được giá trị đúng tại cạnh lên ở T2. Lưu ý, data_valid và async_in được lái theo clock tclk nhưng tclk không được thể hiện ở đây.
Thời điểm từ T3 đến T4, data_en chắc chắn tích cực.
Nếu vi phạm timing tại T1 làm giá trị pre_en ổn định về 1 thì data_en sẽ tích cực trong chu kỳ từ T2 đến T3.
Nếu vi phạm timing tại T3 làm giá trị pre_en ổn định về 1 thì data_en sẽ tích cực trong chu kỳ từ T4 đến T5.
Trường hợp này, độ rộng tối thiểu là data_en là 1 chu kỳ rclk và độ rộng tối đa của data_en là 3 chu kỳ rclk. Để đảm bảo miền clock luôn bắt đúng dữ liệu thì async_in phải duy trì ổn định giá trị trong trường hợp xấu nhất, khi data_en tích cực 3 chu kỳ. Trong ví dụ này, async_in được lái giá trị mới cùng thời điểm cạnh lên của data_valid theo clock tclk và duy trì ổn định trong 5 chu kỳ xung clock rclk. 
5.3) Ưu điểm và chú ý
Ưu điểm:
  • Cơ chế đồng bộ đơn giản
  • Chỉ dùng mạch đồng bộ nhiều FF nối tiếp trên tín hiệu cho phép nên logic đồng bộ tốn ít tài nguyên và công suất tiêu thụ so với cơ chế tự kiểm tra sự ổn định của giá trị
  • Số lượng FF của mạch đồng bộ không đổi khi số lượng bit tín hiệu tăng.
  • Có thể dùng trong trường hợp truyền nhiều giá trị giống nhau liên tục căn cứ trên số lần tích cực của tín hiệu cho phép.
Chú ý:
  • Phải tuân thủ ràng buộc về độ rộng xung của tín hiệu, một giá trị mới phải duy trì trong khoảng thời gian đủ để miền clock nhận chắc chắn bắt đúng giá trị trong trường hợp xấu nhất, trường hợp độ rộng xung tín hiệu cho phép ở ngõ ra mạch đồng bộ, data_en, dài nhất. Điều này làm cho miền clock truyền, tclk, có thể phải thêm logic để duy trì độ rộng xung của các tín hiệu như yêu cầu.
  • Thời gian tối thiểu để đảm bảo miền nhận lấy đúng giá trị mới là "4 chu kỳ rclk + thời gian setup của FF + thời gian hold của FF", tình từ thời điểm T1.
6) Phương pháp chuyển đổi giá trị
6.1) Mô tả nguyên lý
Như đã trình bày, vấn đề của việc đồng bộ một tín hiệu nhiều bit bằng nhiều FF mắc nối tiếp là sinh ra giá trị không mong muốn nếu hiện tượng bất ổn định xảy ra khi nhiều bit cùng thay đổi giá trị.
Để giải quyết vấn đề này, một phương pháp đồng bộ có thể được áp dụng là chuyển đổi giá trị làm thay đổi nhiều bit thành một giá trị chỉ làm thay đổi 1 bit so với giá trị trước đó ở miền clock truyền. Tại miền clock nhận, tín hiệu sẽ được đồng bộ bằng mạch đồng bộ nhiều FF. Sau đó, giá trị ngõ ra mạch đồng bộ được khôi phục lại giá trị ban đầu.

Hình 9: Mạch nguyên lý phương pháp chuyển đổi giá trị thay đổi nhiều bit thành giá trị thay đổi 1 bit trước khi đồng bộ
Trong hình minh họa trên, binary_data là một tín hiệu nhiều bit. Giá trị của binary_data có thể thay đổi nhiều bit cùng lúc. Nó được chuyển đổi thành data_in thông qua một logic chuyển đổi. data_in được đồng bộ bằng mạch đồng bộ 2 FF. Ngõ ra mạch đồng bộ 2 FF là sync_out sẽ đi qua logic khôi phục lại giá trị ban đầu, data_out.
Sau logic chuyển đổi ở miền tclk, một tầng FF có thể được thêm vào giúp loại bỏ độ trễ gây ra do mạch tổ hợp trên đường data_in. Đồng thời loại bỏ glitch có thể sinh ra trên từng bit của tín hiệu nếu có.
6.2) Điều kiện sử dụng
Điều kiện để sử dụng phương pháp này là dữ liệu cần truyền giữa hai miền clock phải thay đổi theo một quy luật cố định, không được thay đổi ngẫu nhiên hay tùy ỳ.
Dữ liệu thay đổi có quy luật có thể là giá trị bộ đếm lên hoặc đếm xuống, máy trạng thái, ... như ví dụ trong "mục 2".
Bộ chuyển đổi thường được dùng trong phương pháp này là bộ chuyển đổi giữa mã binary và Gray. Tuy nhiên, bạn có thể sử dụng bất kỳ loại mã nào miễn là nó phải đảm bảo chỉ thay đổi logic của 1 bit khi giá trị cần truyền thay đổi.
Nếu dữ liệu cần truyền là bộ đếm lên hoặc đếm xuống 1 đơn vị thì nó có thể được chuyển đổi thành mã Gray như trong link tham khảo sau. Việc chuyển đổi ngược từ mã Gray thành binary được thực hiện như mạch nguyên lý sau đây.
Hình 10: Mạch nguyên lý chuyển đổi từ mã nhị phân (binary) thành mã Gray
Tương tự đối với FSM, thay vì mã hóa trạng thái nhị phân, chúng ta có thể chuyển thành mã Gray. Quay lại ví dụ FSM ở mục 2, FSM này chuyển trạng thái theo thứ tự lặp lại IDLE->ACTIVATE->RUN->DEACTIVATE. Trong trường hợp này, chúng ta có thể chuyển giá trị mã hóa trạng thái theo thứ tự mã Gray như sau:
IDLE = 2’b00;
ACTIVATE = 2'b01;
RUN = 2’b11; //thay vì 2'b10
DEACTIVATE = 2’b10; //thay vì 2'b11
Như đã trình bày, tùy vào trường hợp cụ thể, chúng ta có thể dùng bất kỳ loại chuyển đổi nào miễn là tuân thủ quy tắc "hai giá trị liên tiếp chỉ thay đổi mức logic của 1 bit". Xét ví dụ sau đây:
Hình 11: Một FSM mà giá trị trạng thái cần được truyền từ miền clock tclk sang miền clock rclk
Hình trên là một FSM được tạo ra trên miền clock tclk và giá trị trạng thái cần được truyền sang miền clock rclk. FSM có 5 trạng thái nên chúng ta không thể dùng mã hóa Gray thông để mã hóa tuần tự cho từng trạng thái. Trường hợp này, một bộ giá trị sẽ được lựa chọn để đảm bảo khi chuyển trạng thái chỉ có 1 bit thay đổi, tạm gọi cách gán trạng thái này là "custom code".
IDLE                = 3’b000;
ACTIVATE      = 3'b100;
PRE_RUN       = 3’b101;
POST_RUN     = 3'b111;
DEACTIVATE = 3’b110;
Chú ý, trong trường hợp này, khi reset bằng cách tích cực tín hiệu rst_n, thì FSM sẽ về trạng thái IDLE. Nếu FSM đang ở trạng thái PRE_RUN, POST_RUN hoặc DEACTIVATE thì việc nhảy về RESET sẽ làm thay đổi giá trị của 2 bit. Vì vậy, việc gán trạng thái này chỉ được dùng ở một trong hai trường hợp sau:
  • Tín hiệu reset rst_n sẽ reset cả hai miền clock tclk và rclk.
  • Tín hiệu reset rst_n chỉ reset miền tclk nhưng khi miền rclk phát hiện FSM là IDLE thì sẽ khởi tạo lại từ đầu (giống như được reset) hoặc không quan tâm đến các trạng thái hoạt động trước đó. Trường hợp này, cho dù giá trị FSM thay đổi nhiều bit và có thể sinh ra một trạng thái sai khi đồng bộ sang miền rclk nhưng nó không ảnh hưởng đến hoạt động của miền rclk vì miền này sẽ khởi tạo và hoạt động lại.
6.3) Ưu điểm và chú ý
Ưu điểm:
  • Cơ chế đồng bộ đơn giản
  • Hữu dụng cho việc đồng bộ giá trị bộ đếm và FSM đơn giản. FSM đơn giản được hiểu là FSM có số lượng chuyển trạng thái ít và không có nhiều nhánh chuyển trạng thái. Đây là một nhận xét mang tính tương đối.
Chú ý:
  • Chỉ dùng được trong trường hợp giá trị cần truyền giữa hai miền clock thay đổi có quy luật.
  • Tài nguyên dành cho mạch chuyển đổi giá trị lớn khi số bit cần truyền tăng. Khi tài nguyên mạch tổ hợp chuyển đổi giá trị ở miền tclk lớn đáng kể thì cần chèn thêm một tầng FF tại ngõ ra trước khi truyền sang miền rclk, điều này làm tăng công suất tiêu thụ
7) Phương pháp giao thức bắt tay
7.1) Mô tả nguyên lý
Phương pháp dùng giao thức bắt tay (handshake) là phương pháp thông tin hai chiều giữa miền truyền và miền nhận. Miền truyền dữ liệu sẽ gửi dữ liệu kèm một tín hiệu yêu cầu truyền (request), tín hiệu req, đến miền nhận. Miền nhận đồng bộ tín hiệu yêu cầu bằng mạch đồng bộ nhiều FF mắc nối tiếp theo clock rclk. Miền nhận sẽ phát hiện mức tích cực của tín hiệu yêu cầu để nhận dữ liệu. Sau khi nhận dữ liệu, miền nhận sẽ thông báo lại cho miền truyền bằng các tích cực một tín hiệu phản hồi, tín hiệu ack. Miền truyền sẽ đồng bộ lại tín hiệu ack theo clock tclk. Miền truyền sẽ tiếp tục truyền dữ liệu mới sau khi phát hiện ack tích cực.
Hình 12: Mạch nguyên lý của phương pháp đồng bộ bằng giao thức bắt tay
Trong hình minh họa trên đây:
  • Request logic là thành phần quyết định khi nào tích cực tín hiệu req và cập nhật dữ liệu truyền mới dựa trên thông tin từ tín hiệu sync_ack, tín hiệu ack sau khi đã đồng bộ, và trạng thái hoạt động của miền tclk.
  • Ack logic là thành phần quyết định khi nào tích cực tín hiệu ack dựa trên thông tin từ tín hiệu sync_req, tín hiệu req sau khi đã đồng bộ, và trạng thái hoạt động của miền rclk.
Có hai loại giao thức bắt tay là:
  • Bắt tay 2 bước
  • Bắt tay bốn bước
Hình 13: (a) Giao thức bắt tay 2 bước và (b) giao thức bắt tay 4 bước
Giao thức bắt tay 2 bước được thực hiện dựa trên việc lặp lại 2 bước sau đây:
  • (1) Miền tclk, tín hiệu req chuyển trạng thái, từ 0 lên 1 hoặc từ 1 xuống 0,  kèm với dữ liệu cần truyền
  • (2) Miền rclk, sau khi đã nhận được dữ liệu, tín hiệu ack chuyển trạng thái, từ 0 lên 1 hoặc từ 1 xuống 0.
Trong giao thức bắt tay 2 pha, tín hiệu req chỉ chuyển trạng thái khi có dữ liệu cần truyền. Miên tclk chỉ truyền dữ liệu khi đã phát hiện sự chuyển trạng thái trên ack, ứng với thời điểm.
Hình 14: Mạch nguyên lý cơ chế đồng bộ bằng giao thức bắt tay 2 pha
Trong hình vẽ trên:
  • Tại miền tclk: req đảo giá trị và async_in cập nhật giá trị mới cần truyền chỉ khi phát hiện được ack từ miền rclk, sync_ack=req. data_valid là một tín hiệu sinh ra từ logic của miền tclk, chỉ tích cực khi có dữ liệu mới cần truyền.
  • Tại miền rclk: ack đảo giá trị và rec_data cập nhật giá trị mới chỉ khi phát hiện được req từ miền tclk, mức logic của sync_req  khác mức logic của ack. rec_ready là một tín hiệu sinh ra từ logic của miền rclk, chỉ tích cực khi có thể nhận dữ liệu.
Giao thức bắt tay 4 bước được thực hiện dựa trên việc lặp lại 4 bước sau đây:
  • (1) Miền tclk, tín hiệu req tích cực kèm với dữ liệu cần truyền
  • (2) Miền rclk, sau khi đã nhận được dữ liệu, tín hiệu ack tích cực.
  • (3) Miền tclk, tín hiệu req thôi tích cực sau khi đã phát hiện ack tích cực.
  • (4) Miền rclk, tín hiệu ack thôi tích cực sau khi đã phát hiện req thôi tích cực.
Trong giao thức bắt tay 4 pha, mỗi lần truyền dữ liệu req và ack đều phải trải qua 2 trạng thái là tích cực và không tích cực. Một dữ liệu mới chỉ được phép truyền khi miền tclk nhìn thấy cả req và ack đang trong trạng thái không tích cực.
Hình 15: Mạch nguyên lý của có chế đồng bộ bằng giao thức bắt tay 4 pha
Trong hình vẽ trên:
  • Tại miền tclk:
    • req chỉ được tích cực khi sync_ack=req=0 và data_valid=1. data_valid là tín hiệu sinh ra từ logic của miền tclk. Tín hiệu này chỉ tích cực khi miền tclk có dữ liệu muốn truyền. Thời điểm req tích cực cũng là lúc giá trị mới được nạp vào FF async_in.
    • req thôi tích cực khi sync_ack=1.
    • req giữ nguyên giá trị trong các trường hợp khác.
  • Tại miền rclk:
    • ack chỉ được tích cực khi ack=0, sync_req=1 và rec_ready=1. rec_ready là tín hiệu sinh ra từ logic của miền rclk. Tín hiệu này chỉ tích cực khi miền rclk có thể nhận dữ liệu. Thời điểm ack tích cực cũng là lúc giá trị mới được nạp vào FF rec_data.
    • ack thôi tích cực khi sync_req=0.
    • ack giữ nguyên giá trị trong các trường hợp khác
7.2) Điều kiện sử dụng
Phương pháp đồng bộ bắt tay có thể được dùng mà không cần quan tâm đến tỉ lệ tần số hay độ rộng tín hiệu giữa miền truyền (tclk) và miền nhận (rclk) vì nó đảm bảo mỗi dữ liệu được truyền đi từ miền truyền sẽ chắc chắn được nhận bởi miền nhận.
7.3) Ưu điểm và chú ý
Ưu điểm:
  • Không cần quan tâm đến độ rộng tín hiệu khi truyền và nhận
  • Đảm bảo truyền và nhận đúng và đủ số lượng dữ liệu
  • Có thể truyền nhận các dữ liệu có giá trị ngẫu nhiên
  • Mạch logic đơn giản
Chú ý:
  • Nhược điểm của phương pháp này là tốc độ truyền nhận dữ liệu giữa hai miền thấp. Nếu dữ liệu hiện tại chưa hoàn thành thì dữ liệu mới không được phép truyền. Vì vậy, phương pháp này chỉ thích hợp cho các giao tiếp có số lượng dữ liệu truyền nhận ít, không cần xử lý nhanh hoặc dữ liệu truyền có khoảng thời gian giữa hai gói dài
8) Phương pháp bộ nhớ đệm
8.1) Mô tả nguyên lý
Phương pháp này sử dụng một bộ nhớ đệm nằm giữa hai miền truyền và nhận. Miền truyền sẽ ghi dữ liệu cần truyền vào bộ nhớ đệm này bằng clock của miền truyền, tclk. Miền nhận sẽ đọc dữ liệu từ bộ nhớ đệm này bằng clock của miền nhận, rclk.
Hình 16: Nguyên lý dùng bộ nhớ đệm bất đồng bộ
Một loại bộ nhớ đệm thường dùng trong thực tế là FIFO bất đồng bộ (asynchronous FIFO). Hiện có 2 cấu trúc FIFO bất đồng bộ được giới thiệu là:
  • Cấu trúc FIFO bất đồng bộ dùng bộ đếm ghi/đọc Gray.
  • Cấu trúc FIFO bất đồng bộ dùng bộ đếm ghi/đọc token ring.

Hình 17: Cấu trúc của FIFO bất đồng bộ dùng bộ đếm Gray (tài liệu tham khảo 1)
Trong hình minh họa trên, wclk là clock của miền truyền và rclk là clock của miền nhận.
Hình 18: Cấu trúc của FIFO bất đồng bộ dùng bộ đếm token ring (tài liệu tham khảo 2)
Trong hình minh họa trên, CLK_TX là clock miền truyền và CLK_RX là clock miền nhận.
Trong hai loại trên, FIFO bất đồng bộ dùng bộ đếm Gray đang được dùng phổ biến trong các thiết kế chip thực tế. Bài viết sẽ không trình bày chi tiết cấu trúc của hai loại FIFO này. Việc phân tích chi tiết và so sánh hai cấu trúc FIFO sẽ được thực hiện trong một bài viết khác.
8.2) Điều kiện sử dụng
Phương pháp đồng bộ này có thể được dùng mà không cần quan tâm đến tỉ lệ tần số hay độ rộng tín hiệu giữa miền truyền (tclk) và miền nhận (rclk) vì nó đảm bảo mỗi dữ liệu được truyền đi từ miền truyền sẽ chắc chắn được nhận bởi miền nhận.
8.3) Ưu điểm và chú ý
Ưu điểm:
  • Không cần quan tâm đến độ rộng tín hiệu khi truyền và nhận
  • Đảm bảo truyền và nhận đúng và đủ số lượng dữ liệu
  • Có thể truyền nhận các dữ liệu có giá trị ngẫu nhiên
  • Hỗ trợ các giao tiếp cần truyền nhận dữ liệu nhanh với số lượng lớn hoặc các giao tiếp yêu cầu truyền nhận dữ liệu liên tục
Chú ý:
  • Nhược điểm của phương pháp này là phức tạp hơn các phương pháp khác.
  • Tài nguyên và công suất tiêu thụ tăng đáng kể khi dung lượng bộ nhớ tăng
Lịch sử cập nhật:
1) 2020.02.02 - Tạo lần đầu

Tài liệu tham khảo:
1) Clifford E. Cummings, Sunburst Design, Inc; Simulation and Synthesis Techniques for Asynchronous FIFO Design; SNUG, San Jose, 2002
2) Clifford E. Cummings, Peter Alfke; Simulation and Synthesis Techniques for Asynchronous FIFO Design with Asynchronous Pointer Comparisons; SNUG, San Jose, 2002
3) Alessandro Stranoy, Daniele Ludovicix, Davide Bertozzi; A Library of Dual-Clock FIFOs for Cost-Effective and Flexible MPSoC Design; IEEE 2010

Chủ Nhật, 26 tháng 1, 2020

[SV for Syn] Bài 2- Giá trị không xác định kích thước (unsized literal)

Tiếp theo bài 1, bài viết này sẽ phân tích chi tiết về giá trị (số) không xác định kích thước (unsized literal value/number) trong System Verilog (SV). Đồng thời, chúng sẽ được so sánh với Verilog HDL để làm rõ các ưu nhược điểm khi dùng để mô tả RTL code.

Danh sách bài viết liên quan:
Bài 1: Những điểm mới của System Verilog khả tổng hợp

1) Khái niệm literal
Literal, còn gọi là literal constant, là giá trị thể hiện chính nó. Literal là một giá trị cố định, không thay đổi.
Ví dụ, số 8 là một literal đại diện cho chính nó và chỉ thể hiện một giá trị là 8, không thể là 9 hay bất kỳ ý nghĩa nào khác. 8 là một literal nguyên trong SV. Một ví dụ khác, chuỗi ký tự “good” là một literal chuỗi trong SV.
Literal còn được gọi là literal constant nhưng nó không phải là hằng số (constant). Nghĩa là literal constant hay literal là khác với constant. Constant là đối tượng dữ liệu được định danh theo tên (named data object). Trong SV, một hằng số có thể được khai báo bằng parameter, localparam, specparam hoặc const.
Ví dụ 1: Phân biệt literal và hằng số (constant)
parameter WIDTH = 32;
Trong ví dụ trên, WIDTH là một hằng số còn 32 là một literal. 32 luôn là 32 còn WIDTH có thể được gán giá trị lại bằng một literal khác.
2) Phân loại literal
SV chia literal thành các nhóm sau:
  • Số (number):
    • Literal nguyên (integer literal constant) có thể được biểu diễn dưới dạng thập phân (decimal), thập lục phân (hexadecimal), bát phân (octal) hoặc nhị phân (binary)
    • Literal thực (real literal constant) được biểu diễn theo chuẩn IEEE Std 754 về số dấu chấm động có độ chính xác kép.
  • Literal thời gian (time literal) là một giá trị realtime được tỷ lệ theo đơn vị thời gian (fs, ps, ns, us, ms, s) hiện tại và được làm tròn theo sai số thời gian hiện tại  
  • Literal chuỗi (string literal) là một chuỗi ký tự đặt trong cặp dấu ngoặc kép, “ ”, các ký tự không thể in được và ký tự đặc biệt sẽ đi kèm dấu “\” đằng trước 
  • Literal cấu trúc (structure literal) là các mẫu gán hoặc biểu thức cấu trúc với các thành phần là hằng số.
  • Literal mảng (arrayliteral) là các mẫu gán hoặc biểu thức mảng với các thành phần là hằng số.
Ví dụ 2: Minh họa các loại literal
8’h9a //literal nguyên
1.30e-2 //literal thực
2.1ps //literal thời gian
“Hello word\n” //literal chuỗi 
typedef struct {int a; shortreal b;} ab;
ab c; 
c = '{0, 0.0}; //literal cấu trúc
int n[1:2][1:3] = '{'{0,1,2},'{3{4}}}; //literal mảng
Trong ví dụ trên,
  • 2.1ps là một literal thời gian có giá trị là 2.1 và đơn vị là ps
  • “Hello world\n”, không bao gồm dấu ngoặc kép, là literal chuỗi. Trong đó, “\n” là ký tự xuống dòng
  • ‘{0, 0.0} là một literal cấu trúc được dùng để gán giá trị cho biến c được định nghĩa bởi kiểu dữ liệu struct ab.
  • '{'{0,1,2},'{3{4}}} là một literal mảng dùng để gán giá trị cho các phần tử có kiểu dữ liệu là int trong mảng n.
3) Giá trị không xác định kích thước
Một giá trị không xác định kích thước là một giá trị nguyên, thuộc integer literal, là một giá trị không được xác định rõ số bit biểu diễn giá trị. Một giá trị không xác định kích thước có thể là một trong hai dạng sau:
  • Một số thập thân đơn thuần (simple decimal number).
  • Một số nguyên được biểu diễn dưới dạng <size><base><value> nhưng thiếu trường <size>
Ví dụ 3: Giá trị không xác định kích thước
9 //số thập phân đơn thuần
‘h8 //số nguyên thiếu mô tả độ rộng bit
Trong ví dụ trên, 9 là một số thập phân đơn thuần, giá trị này không chỉ rõ số bit biểu diễn nó là bao nhiêu. Nó khác với cách biểu diễn 8’d9. Ở cách biểu diễn này, giá trị 9 được chỉ rõ được biểu diễn bằng 8 bit.
‘h8 là một giá trị thập lục phân nhưng khuyết trường <size> nên cũng không thể xác định cụ thể số bit biểu diễn giá trị này là bao nhiêu.
Cả Verilog HDL và SV đều hỗ trợ biểu diễn số không xác định kích thước. Nó được gọi là unsized number, hoặc unsized literal constant, hoặc unsized literal hoặc unsized literal value.
4) Số không xác định kích thước trong Verilog
Nội dung của mục này căn cứ trên phiên bản Verilog 2005.
Verilog HDL, không định nghĩa rõ ràng khái niệm literal. Khái niệm được sử dụng là số (number). Number được trình bày ở mục “3.5 Numbers” và được định nghĩa bằng syntax 3-1. Một số trong Verilog có thể là số thập phân, số bát phần, số thập lục phân, số nhị phân hoặc số thực.
number ::=decimal_number| octal_number| binary_number| hex_number| real_number  
Số không xác định kích thước thường dùng trong các trường hợp sau:
  • Gán đến một tín hiệu hoặc hằng số có kích thước (độ rộng bit) thay đổi
  • Gán cùng một giá trị (0, x hoặc z) đến tất cả các bit trong một tín hiệu hoặc hằng số
Ví dụ 4: Gán giá trị không xác định kích thước
reg [WIDTH-1:0] regData;
regData = 9; //Gán bằng 9
regData = ‘b0; //Gán tất cả các bit bằng 0
regData = ‘bx; //Gán tất cả các bit bằng x
regData = ‘hx9; //Gán 4 bit cuối là 1001, các bit khác là x
regData = ‘bz; //Gán tất cả các bit bằng z
regData = ‘hz8; //Gán 4 bit cuối là 1000, các bit khác là z
regData = ‘b1; //Gán bit 0 bằng 1 và các bit khác bằng 0
Trong ví dụ trên, regData là một tín hiệu có độ rộng bit thay đổi theo giá trị WIDTH. Độ rộng này được gán một lần trước khi biên dịch hoặc tổng hợp code. Giả sử động rộng bit là 16 bit. thì kết quả các phép gán theo thứ tự như sau:
# --- regData = 0000000000001001
# --- regData = 0000000000000000
# --- regData = xxxxxxxxxxxxxxxx
# --- regData =   xxxxxxxxxxxx1001
# --- regData =   zzzzzzzzzzzzzzzz
# --- regData =   zzzzzzzzzzzz1000
# --- regData = 0000000000000001
Trong đó:
  • Phép gán thứ 1, dù độ rộng bit là bao nhiêu thì regData cũng được gán bằng 9
  • Phép gán thứ 2,  dù độ rộng bit là bao nhiêu thì tất cả các bit của regData đều được gán bằng 0
  • Phép gán thứ 3,  dù độ rộng bit là bao nhiêu thì tất cả các bit của regData đều được gán bằng x
  • Phép gán thứ 4,  bốn bit cuối sẽ được gán bằng 9 vì biểu diễn kiểu hex còn tất cả các bit cao đều được gán bằng x
  • Phép gán thứ 5,  dù độ rộng bit là bao nhiêu thì tất cả các bit của regData đều được gán bằng z
  • Phép gán thứ 6,  bốn bit cuối sẽ được gán bằng 8 vì biểu diễn kiểu hex còn tất cả các bit cao đều được gán bằng z
  • Phép gán thứ 7,  chỉ 1 bit cuối sẽ được gán bằng 1 vì biểu diễn kiểu binary còn tất cả các bit cao đều được gán bằng 0
Việc gán giá trị được dựa trên nguyên tắc như sau:
  • Đối với số thập phân đơn thuần thì tất cả các bit được gán phải thể hiện đúng giá trị số thập phân
  • Đối với dạng biểu diễn <base><value>, ví dụ ‘b0, nguyên tắc mở rộng bit được áp dụng như sau:
    • x nếu bit ngoài cùng bên trái của value là x
    • z nếu bit ngoài cùng bên trái của value là z
    • 0 nếu ngoài 2 trường hợp trên
Verilog không hỗ trợ việc mô tả một số không xác định kích thước giúp gán tất cả các bit của một tín hiệu bằng 1. Để gán tất cả các bit của một tín hiệu, có độ rộng thay đổi, bằng 1 trong Verilog, hai cách sau có thể sử dụng:
  • Mô tả giá trị toàn bit 1 với độ rộng cụ thể đối với từng trường hợp
  • Dùng toán tử lặp (replication) “{{}}”
  • Dùng toán tử bù bitwise (Bitwise negation) ~
Ví dụ 5: Các cách gán 1 đến tất cả các bit của một tín hiệu có giá trị thay đổi
parameter WIDTH = 16;
reg [WIDTH-1:0] regData;
regData = 16'hffff; //Mô tả đúng độ rộng bit
regData = {WIDTH{1'b1}}; //Dùng toán tử lặp
regData = ~0; //Dùng toán tử bù bitwise
Ví dụ trên là một số cách có thể áp dụng để gán giá trị toàn bit 1 đến một tín hiệu.
  • Cách thứ 1 phải chỉ rõ độ rộng bit của tín hiệu regData. Bất lợi lớn nhất của cách này là khi độ rộng bit tín hiệu thay đổi thì trường <size> của giá trị cũng phải thay đổi theo
  • Cách thứ 2 phải biết hằng số khai báo độ rộng WIDTH của tín hiệu regData.
  • Cách thứ 3 là một "mẹo" viết code
Tóm lại, việc gán cùng một giá trị 0, 1, x hoặc z đến tất cả các bit của một tín hiệu có thể thực hiện trên Verilog nhưng thông qua các cách “cần phải nhớ nguyên tắc và thủ thuật”. Trong các ví dụ đã trình bày, việc này đạt được thông qua cơ chế mở rộng bit hoặc dùng các toán tử khác kèm theo hoặc phải chỉ rõ độ rộng bit.
5) Số không xác định kích thước trong System Verilog
SV định nghĩa rõ ràng khái niệm literal. Trong SV 2017, khái niệm này định nghĩa trong mục “5.7 Numbers”. Tuy SV vẫn bắt đầu với khái niệm number nhưng căn cứ theo cú pháp "Syntax 5-2" thì number chỉ là một phần của literal.
primary_literal ::= number | time_literal | unbased_unsized_literal | string_literal
Trong đó, number bao gồm literal nguyên và literal thực, ứng với số nguyên (integer number) và số thực (real number) trong Verrilog.
Một điểm chú ý khác là, tuy tên của “syntax 5-2” là “Syntax for integer and real numbers” giống với “syntax 3-1” trong Verilog nhưng SV đã định nghĩa lại các điểm sau:
  • syntax 5-2 định nghĩa primary_literal đã bao gồm cả syntax 3-1 định nghĩa number
  • syntax 5-2 bổ sung thêm time_literal, unbased_unsized_literal và string_literal
Như vậy, việc sử dụng tên cũ là “Syntax for integer and real numbers” trong SV dễ gây hiểm nhầm là literal chỉ gồm số nguyên và số thực. Bạn đọc cần chú ý điểm này khi đọc tài liệu SV.
Hình 1: Khái niệm số (number) trong Verilog (trái) và khái niệm literal trong SV (phải)
Ngoài các đặc điểm đã về số không xác định kích thước như Verilog. SV đã nâng cấp để hỗ trợ thêm loại giá trị không xác định kích thước (unsized) và không xác định cơ số (unbased), gọi là unbased unsized literal. Loại này là một giá trị đơn bit (single bit). Nó được dùng để thiết lập cùng một giá trị cho tất cả các bit của tín hiệu hoặc hằng số.
Ví dụ 6: Loại số không xác định kích thước và cơ số
‘0 //Gán 0 đến tất cả các bit được gán bên vế trái
‘1 //Gán 1 đến tất cả các bit được gán bên vế trái
‘x //Gán x (unknown) đến tất cả các bit được gán bên vế trái
‘X //Gán x (unknown) đến tất cả các bit được gán bên vế trái
‘z //Gán z (high-impedance) đến tất cả các bit được gán bên vế trái
‘Z //Gán z (high-impedance) đến tất cả các bit được gán bên vế trái
Chú ý, dấu nháy được sử dụng là dấu apostrophe, còn gọi là tick, nằm cạnh phím enter trên bàn phím chứ không phải dấu back tick, nằm cạnh số 1 trên bàn phím.
Ví dụ 7: Gán giá trị không xác định kích thước và cơ số
parameter WIDTH = 64;
reg [WIDTH-1:0] regData;
initial begin
  regData = '0;
  $display("--- regData = %b", regData);
  //
  regData = '1;
  $display("--- regData = %b", regData);
  //
  regData = 'x;
  $display("--- regData = %b", regData);
  //
  regData = 'z;
  $display("--- regData = %b", regData);
end
Kết quả của ví dụ trên đây:
# --- regData = 000000000000
# --- regData = 111111111111
# --- regData = xxxxxxxxxxxx
# --- regData = zzzzzzzzzzzz
Với nâng cấp mới này, khi cần gán một giá trị toàn bit 0, 1, x hoặc z đến một tín hiệu trong SV, chúng ta không cần “nhớ” xem tín hiệu này bao nhiêu bit, không cần mô tả kích thước và cơ số. Đặc biệt, trường hợp gán toàn bit 1 sẽ không cần dùng thêm toán tử như Verilog.
Hình 2: Số không xác định kích thước trong Verilog (trái) được mở rộng thêm trong SV (phải)

Lịch sử cập nhật:
1) 2019.1.10 - Tạo lần đầu

Tài liệu tham khảo:
1.  IEEE Computer Society and  IEEE Standards Association Corporate Advisory Group;  IEEE Standard for SystemVerilog—  Unified Hardware Design,  Specification, and Verification  Language; 06.Dec.2017
2. IEEE Computer Society;   IEEE Standard for Verilog® Hardware Description Language,  Specification, and Verification  Language; 07.Apr.2006
3. Stuart Sutherland, Simon Davidmann, Peter Flake; A Guide to Using SystemVerilog for Hardware Design and Modeling; 2006.
5. Synopsys; HDL Compiler for SystemVerilog User Guider; March 2012.
6. Thiết kế vi mạch số với VHDL & Verilog-Tống Văn On.

Chủ Nhật, 12 tháng 1, 2020

[Q&A] Hỏi đáp về công nghệ vi mạch trong quý 1 năm 2020

Tập hợp các câu hỏi và trả lời về công nghệ vi mạch thảo luận trong nhóm VLSI Technology.
Phần mềm sử dụng:
  • Tổng hợp: Quartus II 32-bit Version 12.0 Build 178 05/31/2012
  • Mô phỏng: QuestaSim-64 10.2c


Câu hỏi 1 (2020.01.01):
Khi mô tả phần cứng bằng ngôn ngữ Verilog HDL, Latch được tạo ra trong trường hợp nào? và làm thế nào để tránh tạo Latch không mong muốn?

Trả lời 1a:
Khi mô tả phần cứng bằng ngôn ngữ Verilog HDL, Latch được tạo ra trên một ngõ ra khi thuộc một trong các trường hợp sau:
  1. Ngõ ra được mô tả bằng các phát biểu điều kiện if/else hoặc case (case/casex/casez) trong khối always dùng danh sách độ nhạy theo mức nhưng không được gán giá trị hoặc được gán bằng chính nó ở một trong các tổ hợp có thể xảy ra của điều kiện. Giải thích từ ngữ:
    • Sử dụng always có danh sách độ nhạy theo mức, không phải theo cạnh (posedge hoặc negedge)
    • Thiếu gán giá trị ở một trong các tổ hợp có thể xảy ra của điều kiện. Ví dụ, điều kiện là sel[1:0] có 2 bit thì có 4 tổ hợp có thể xảy ra là b00, b01, b10 và b11. Chú ý, các tổ hợp này không bao gồm giá trị x (don't care) và z (high impedance).
    • "Gán bằng chính nó" (tồn tại đường hồi tiếp từ ngõ ra) là ngõ ra được gán lại bằng chính ngõ ra mà không tạo ra bất cứ logic nào khác, ví dụ như y=y.
    • "Ở một trong các tổ hợp điều kiện" chứ không phải trên toàn bộ các tổ hợp có thể của điều kiện. 
  2. Ngõ ra được mô tả bằng toán tử điều kiện "?" trong khối always hoặc phát biểu assign và được gán bằng chính nó ở một trong các nhánh điều kiện.
Latch được tạo ra để "giữ lại giá trị ngõ ra trước đó" khi điều kiện rơi vào tổ hợp mà tại đó ngõ ra không được gán giá trị.

Ví dụ 1: Tạo Latch khi thiếu ít nhất một tổ hợp giá trị của điều kiện
always @ (*) begin
  if (sel == 2'd0) y = a;
  else if (sel == 2'd1) y = b;
  else if (sel == 2'd2) y = 0;
end
hoặc
always @ (*) begin
  case (sel)
    2'd0: y = a;
    2'd1: y = b;
    2'd2: y = 0;
  endcase
end
Trong ví dụ 1, Latch được tạo ra vì tổ hợp giá trị 2'd3 của tín hiệu điều kiện sel[1:0] không được mô tả. Trong trường hợp này, khi sel=2'd3 thì y sẽ giữ nguyên giá trị trước đó và Latch được tạo ra khi tổng hợp.
Hình 1: Mạch logic của ví dụ 1, Latch xuất hiện ở ngõ ra y
Trong hình trên, tín hiệu enable của Latch là ENA chỉ tích cực khi sel = 0, 1 hoặc 2. Nếu sel=3, ENA không tích cực và y giữ nguyên giá trị cũ.
Ví dụ 2: Tạo Latch vì thiếu gán giá trị ở một trong các tổ hợp điều kiện
always @ (*) begin
  case (sel[1:0])
    2'd0: begin
y = a;
x = b;
    end
   2'd1: begin
y = b;
x = a;
   end
   2'd2: begin
       y = 0;
       x = 1;
   end
   2'd3: begin
       x = 0;
   end
  endcase
end
Trong ví dụ 2, tất cả các tổ hợp điều kiện đã được mô tả. Tuy nhiên, ngõ ra y không được gán giá trị ở tổ hợp sel=3 nên Latch được tạo ra trên ngõ ra y.
Hình 2: Mạch logic của ví dụ 2, Latch được tạo ra ở ngõ ra y 
Ví dụ 3: Tạo Latch vì gán giá trị ngõ ra bằng chính nó
assign y = (sel==0)? a: y;
hoặc
always @ (*) begin
  if (sel == 2'd0) y = a;
  else y = y;
end
Trong ví dụ 3, ngõ ra y được gán bằng chính nó khi sel khác 0.
Hình 3: Mạch logic của ví dụ 3, Latch được tạo ra trên ngõ ra y
Ví dụ 4: Tạo Latch khi ngõ ra được gán bằng chính nó kèm toán tử logic
always @ (*) begin
  if (sel == 2'd0) y = a;
  else y = &y;
end
Trong ví dụ 4, ngõ ra y=&y nhưng toán tử "&" không có tác dụng tạo ra một cổng logic vì y là tín hiệu 1 bit. Dòng code này tương đương với y=y nên Latch được tạo ra giống như ví dụ 3.
Nếu ngõ ra được gán bằng chính nó và kèm với mô tả có tác dụng tạo ra cổng logic như NOT, OR, AND, XOR, ... thì một mạch tuần tự bất đồng bộ (asynchronous circuit) sẽ được tạo ra chứ không phải Latch.
Ví dụ 5: Ngõ ra được gán với bù của chính nó
always @ (*) begin
  if (sel == 2'd0) y = a;
  else y = ~y;
end
Trong ví dụ 5, ngõ ra y=~y, một logic cần được tạo ra, ví dụ như cổng NOT, để thực hiện đảo giá trị của y. Trường hợp này, Latch không được tạo ra ở y. Chú ý, trong các thiết kế đồng bộ, loại mạch tuần tự bất đồng bộ như ví dụ này không được sử dụng.
Hình 4: Mạch logic của ví dụ 5, mạch tuần tự bất đồng bộ được tạo ra ở y
Đối với trường hợp gán ngõ ra bằng chính nó, nếu phép gán này được thực hiện trên tất cả các tổ hợp điều kiện có thể thì Latch cũng không được tạo ra.
Ví dụ 6: Gán ngõ ra bằng chính nó trên tất cả các tổ hợp điều kiện
always @ (*) begin
  if (sel == 2'd0) y = &y;
  else if (sel == 2'd1) y = ^y;
  else if (sel == 2'd2) y = y;
  else y = |y;
end
Trong ví dụ 6, ngõ ra y=y trong tất cả các tổ hợp điều kiện. Điều này ứng với ngõ ra y không được lái bởi bất cứ ngõ vào nào. Tùy trình tổng hợp y có thể được gán bằng 0 hoặc 1.

Trả lời 1b:
Như vậy để tránh tạo Latch không mong muốn trên một ngõ ra thì:
  • Ngõ ra phải được gán giá trị trong tất cả tổ hợp của điều kiện khi mô tả logic dùng phát biểu điều kiện trong khối always có danh sách độ nhạy theo mức.
  • Nếu tồn tại đường hồi tiếp (feedback) từ ngõ ra thì phải có logic trên đường hồi tiếp này. Lúc này, một mạch tuần tự bất đồng bộ được tạo ra. Chú ý, trong thiết kế đồng bộ, chúng ta sẽ không sử dụng loại mạch tuần tự bất đồng bộ này. Điều này được nêu ở đây chỉ với mục đích "tránh tạo Lacth".
  • Sử dụng phát biểu function
  • Sử dụng thuộc tính (attribute) full_case đối với phát biểu case để chỉ dẫn tổng hợp
Ví dụ 7: Gán giá trị ngõ ra trong tất cả tổ hợp của điều kiện trong phát biểu case
always @ (*) begin
  case (sel)
    2'd0: y = a;
    2'd1: y = b;
    2'd2: y = 0;
    2'd3: y = 1;
  endcase
end
Trong ví dụ trên, ngõ ra y được gán giá trị ở tất cả tổ hợp điều kiện trong phát biểu case.
Hình 5: Mạch logic của ví dụ 7
Ví dụ 8: Gán giá trị ngõ ra ở tất cả tổ hợp điều kiện trong phát biểu if/else, trường hợp sel chỉ là 1 bit
always @ (*) begin
  if (sel) y = a;
  else if (!sel) y = b;
end
Trong ví dụ trên, sel chỉ 1 bit, y được gán giá trị trong cả 2 trường hợp sel=0 và sel=1. Trình tổng hợp Quartus II không tạo Latch trong trường hợp này dù có warning.
Warning (10240): Verilog HDL Always Construct warning at latch_ref.v(95): inferring latch(es) for variable "y", which holds its previous value in one or more paths through the always construct
Hình 6: Mạch logic của ví dụ 8
Ví dụ 9: Gán giá trị ngõ ra ở tất cả tổ hợp điều kiện trong phát biểu if/else, trường hợp sel là 2 bit
always @ (*) begin
  if (sel == 2'd0) y = a;
  else if (sel == 2'd1) y = b;
  else if (sel == 2'd2) y = 0;
  else if (sel == 2'd3) y = 1;
end
Trong ví dụ trên, y được gán giá trị ở tất cả tổ hợp có thể của điều kiện trong phát biểu if/else. Chúng ta mong muốn Latch không được tạo ra nhưng với một điều kiện có số bit nhiều hơn 1 thì trình tổng hợp có thể vẫn tạo Latch trong trường hợp này.
Hình 7: Mạch logic của ví dụ 9, Latch được tạo ra dù ngõ ra được gán ở tất cả các tổ hợp có thể của điều kiện trong pahts biểu if/else
Nếu bạn sử dụng Design Compiler (DC) của Synopsys thì kết quả có thể tương tự như Quartus II. Đối với phát biểu if/else trong always, DC sẽ tạo Latch nếu thiếu mô tả nhánh else.
When an if statement used in a Verilog always block or VHDL process as part of a continuous assignment does not include an else clause, Design Compiler creates a latch. 
Như vậy đối với phát biểu if, việc mô tả đầy đủ các tổ hợp điều kiện mà không có nhánh else không đảm bảo tránh tạo Latch mà tùy thuộc vào trình tổng hợp. Tương tự, đối với phát biểu case nên dùng default để đảm bảo đã đầy đủ tất cả các tổ hợp điều kiện.
Ví dụ 10: Tránh tạo Latch không mong muốn trong phát biểu case bằng cách dùng default
always @ (*) begin
  case (sel)
    2'd0: y = a;
    2'd1: y = b;
    2'd2: y = 0;
    default: y = 1;
  endcase
end
Việc "gán giá trị trong tất cả tổ hợp của điều kiện" còn có thể đạt được bằng cách khác là mô tả giá trị mặc định của ngõ ra trước các phát biểu if và case. Cách mô tả này có tác dụng tương đương như việc sử dụng else hoặc default.
Ví dụ 11: Mô tả giá trị mặc định của ngõ ra trước khi mô tả phát biểu điều kiện để tránh tạo Latch
always @ (*) begin
  y = 0;
  if (sel == 2'd0) y = a;
  else if (sel == 2'd1) y = b;
end
hoặc:
always @ (*) begin
  y = 0;
  case (sel)
    2'd0: y = a;
    2'd1: y = b;
  endcase
end
Trong ví dụ trên, y=0 nếu sel[1:0] khác 0 và khác 1.
Hình 8: Mạch logic của phát biểu case trong ví dụ 11
Một cách khác để tránh tạo Latch không mong muốn mà không cần phải mô tả đầy đủ các tổ hợp điều kiện có thể là dùng phát biểu function. Giá trị ngõ ra sẽ được gán giá trị 0 hoặc 1 tùy trình tổng hợp nếu nó không được gán trong tất cả các tổ hợp điều kiện.
Ví dụ 12: Sử dụng function để tránh tạo Latch không mong muốn
always @ (*) begin
  y = out(sel, a, b);
end 
function reg out;
  input [1:0] in0;
  input in1, in2;

  case (in0)
    2'd0: out = in1;
    2'd1: out = in2;
  endcase

endfunction
hoặc:
always @ (*) begin
  y = out(sel, a, b);
end

function reg out;
  input [1:0] in0;
  input in1, in2;

  if (in0 == 2'd0) out = in1;
  else if (in0 == 2'd1) out = in2;

endfunction
Trong ví dụ trên, function out chỉ xác định giá trị ngõ ra trong 2 trường hợp khi in0=0 và in0=1. Khi tổng hợp, y=out sẽ có giá trị 0 hoặc 1 nếu sel=in0=2 hoặc sel=in0=3.
Hình 9: Mạch logic cho function dùng phát biểu if trong ví dụ 12, y được gán 0 khi sel khác 0 và khác 1
Một cách khác để đạt được kết quả tổng hợp như function là dùng thuộc tính chỉ dẫn tổng hợp full_case cho phát biểu case.
Ví dụ 13: Dùng thuộc tính full_case để tránh tạo Latch không mong muốn
always @ (*) begin
  (* full_case *)
  case (sel)
    2'd0: y = a;
    2'd1: y = b;
  endcase
end
hoặc:
always @ (*) begin
  case (sel) // synthesis full_case
    2'd0: y = a;
    2'd1: y = b;
  endcase
end
Trong ví dụ trên, cách mô tả thuộc tính dùng (* *) được khuyến cáo sử dụng vì các trình mô phỏng và tổng hợp có thể kiểm tra lỗi cú pháp (syntax) nếu mô tả sai. Cách mô tả thuộc tính như một comment, dùng //, không được khuyến khích vì:
  • Nếu viết sai chính tả ví dụ như full_case thanh fu_case thì trình tổng hợp sẽ bỏ qua, xem như đó là 1 comment và không kiểm tra syntax
  • Mặt khác, tùy trình tổng hợp, việc hỗ trợ từ khóa cho comment này khác nhau. Ví dụ, DC hỗ trợ // synopsys ... còn Quartus II hỗ trợ cả // synthesis ... và // synopsys ...
Hình 10: Mạch logic cho ví dụ 13
Trong mạch logic trên, nếu sel=2 hoặc sel=3, ngõ ra mạch so sánh bằng 0 và y=a.
Việc dùng function hoặc thuộc tích full_case để mô tả một mạch tổ hợp không có đầy đủ các tổ hợp điều kiện trong một số trường hợp sau:
  • Tạo ra một mạch không cần quan tâm (don't care) đến giá trị ngõ ra trong một số tổ hợp điều kiện
  • Hoặc tạo ra một mạch có một vài tổ hợp điều kiện không bao giờ xảy ra
Khi dùng với một trong hai mục đích trên, mạch tổ hợp có thể được mô tả bằng một cách tương đương khác là gán giá trị "x" cho ngõ ra trong các trường hợp không sử dụng. Điều này vừa tránh tạo Latch, vừa tạo được logic tối ưu như mong muốn.
Ví dụ 14: Gán giá trị "x" cho ngõ ra
always @ (*) begin  case (sel)
    2'd0: y = a;
    2'd1: y = b;
    default: y = 1'bx;  endcaseend
Việc gán giá trị "x" cho ngõ ra trong các trường hợp điều kiện không sử dụng hoặc không quan tâm giá trị sẽ giúp trình tổng hợp có thể tối ưu logic tốt hơn, nhất là khi số bit điều kiện và số bit ngõ ra lớn. Tuy nhiên, một điểm bất lợi là kết quả mô phỏng giữa mức RTL code và mức cổng (gate netlist) sẽ khác nhau vì khi rơi vào nhánh gán giá trị "x".
  • Mức RTL code, ngõ ra là "x"
  • Mức cổng, ngõ ra là 0 hoặc 1
Điểm bất lợi này cũng xảy ra với phát biểu function, như ví dụ 12.
  • Mức RTL code, ngõ ra giữ nguyên giá trị trước đó, hành vi này giống như ngõ ra có Latch
  • Mức cổng, ngõ ra là 0 hoặc 1
Ngoài ra:
Đối với System Verilog, bạn có thể dùng unique case hoặc priority case để tránh tạo Latch khi mô tả mạch tổ hợp. Nếu  unique hoặc priority được sử dụng thì case không cần phải mô tả đầy đủ các trường hợp nhưng Latch vẫn sẽ không tạo ra. Sự khác nhau của unique và priority, các bạn tham khảo trong tài liệu SV.

Ví dụ 15: Dùng unique hoặc priority case để tránh tạo Latch
  unique case (sel)
    2'd0: y = a;
    2'd1: y = b;
  endcase
hoặc
  priority case (sel)
    2'd0: y = a;
    2'd1: y = b;
  endcase
Hình 11: Mạch logic của ví dụ 15



Câu hỏi 2 (2020.01.06):
Khi truyền 1 tín hiệu 1 bit, async_in, từ miền clock A sang miền clock B, tại miền clock B, tín hiệu được đồng bộ bằng 2 Flip-Flop theo miền clock B. Điều này giúp loại bỏ hiện tượng metastable ở ngõ ra mạch đồng bộ, synch_out. Đúng hay sai?

Hình 11: Mạch đồng bộ tín hiệu 1 bit dùng 2 Flip-Flop

Trả lời câu 2:
SAI.
Hiện tượng tín hiệu ngõ ra synch_out bất ổn định (metastable) không thể bị loại bỏ hoàn toàn bởi mạch đồng bộ 2 FF. Mạch đồng bộ 2 FF chỉ có thể "hạn chế" việc xảy ra hiện tượng này. Đây là hiện tượng có tính chất xác suất. Việc mắc nối tiếp 2 hoặc nhiều FF để đồng bộ tín hiệu 1 bit giúp giảm xác suất xảy ra hiện tượng bất ổn định.
Chi tiết được trình bày ở mục 4. MTBF trong bài viết "đồng bộ tín hiệu bất đồng bộ".



Câu hỏi 3 (2020.01.13): 
Trình bày về phép gán blocking và non-blocking trong ngôn ngữ Verilog HDL (định nghĩa, cách sử dụng, hoạt động, những lưu ý, ...)?

Trả lời câu 3: Trả lời này gồm 3 phần
  1. Trình bày về phép gán blocking và nonblocking
  2. Phân tích việc sử dụng 2 phép gán khi mô tả mạch tổ hợp
  3. Phân tích việc sử dụng 2 phép gán khi mô tả mạch tuần tự
1) Tổng quan

Phép gán blocking (chặn) và nonblocking (không chặn) là loại phát biểu gán thủ tục (procedural assignment statement). Các phép gán này được sử dụng trong các thủ tục always, initial, task và function.
Ký hiệu:
  • Phép gán blocking: = (Verilog) và các toán tử gán mới +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, <<<=, >>>= (System Verilog)
  • Phép gán nonblocking: <=
Trong một khối tuần tự (sequential block), một phép gán blocking phải được thực thi trước khi thực thi các phát biểu khác mô tả sau phép gán này. Nghĩa là, phép gán blocking chặn (block) sự thực thi của các phát biểu nằm phía sau nó. Khối tuần tự là khối được đóng gói trong cặp từ khóa begin/end.
Trong một khối song song (parallel block), một phép gán blocking không chặn sự thực thi của các phát biểu khác được mô tả sau phép gán này. Khối song song là khối được định nghĩa trong cặp từ khóa fork/(join/join_any/join_none), gọi chung là fork/join.
Phép gán nonblocking cho phép sắp xếp việc thực thi gán giá trị mà không chặn (block) sự thực thi của các phát biểu khác mô tả sau phép gán này. Trong tài liệu IEEE, nó là “without blocking the procedural flow”, nghĩa là không chặn quá trình thực thi các thủ tục.
Theo các quy định trên đây về phép gán blocking và nonblocking thì các nhận xét chung chung như “Phép gán blocking được thực hiện tuần tự hay thực thi theo thứ tự còn phép gán nonblocking được thực hiện song song” là chưa chính xác vì hai phép gán này đều có thể thực hiện tuần tự hoặc đồng thời tùy thuộc vào việc nó được mô tả trong một khối begin/end hay fork/join.
2) Hoạt động của phép gán blocking và nonblocking
Verilog và System Verilog là ngôn ngữ xử lý theo mô hình sự kiện rời rạc. Một quá trình mô phỏng được chia thành các khe thời gian. Mỗi khe thời gian được chinh thành nhiều miền sự kiện khác nhau. Mỗi sự kiện sẽ được sắp xếp (schedule) vào các miền này để thực thi. Các bạn đọc thêm ở bài viết về các miền sự kiện của Verilog và System Verilog.

Phép gán blocking sẽ được thực thi việc ước lượng giá trị vế phải và gán sang vế trái trong một bước trong miền ACTIVE của 1 khe thời gian. Trong khi đó, phép gán nonblocking được thực thi bằng 2 bước:
  • Ước lượng giá trị bên vế phải phép gán tại miền ACTIVE và sắp xếp việc gán giá trị vào miền NBA (NonBlocking Assigment) của một khe thời gian
  • Gán giá trị từ vế phải sang vế trái tại miền NBA
Hình 1: Miền thực thi của phép gán blocking và nonblocking trong một khe thời gian Verilog (trái) và SV (phải)
Ví dụ sau đây sẽ giải thích sự khác nhau của phép gán blocking và nonblocking.
Ví dụ 1: Phép gán blocking
initial begin
  y = #5 1'b1;
  y = #3 1'b0;
end
initial fork
  x = #5 1'b1;
  x = #3 1'b0;
join
Trong ví dụ trên y được gán giá trị trong một khối tuần tự begin/end và x được gán giá trị trong một khối song song fork/join. Một giá trị delay được dùng trên mỗi phép gán.
Đối với y, sau 5 đơn vị thời gian, y sẽ được gán bằng 1. Sau 3 đơn vị thời gian nữa, T=8, y được gán bằng 0. Điều này là vì, trong một khối tuần tự, các phép gán blocking sẽ thực hiện tuần tự từ trên xuống, phép gán y = #5 1’b1 phải thực hiện trước rồi mới đến y = #3 1’b0. Trường hợp này, phép gán y = #5 1’b1 chặn sự thực thi của phép gán y = #3 1’b0.
Đối với x, sau 3 đơn vị thời gian x=0 và sau thêm 2 đơn vị thời gian (thời điểm t=5) nữa thì x=1. Điều này là vì, trong một khối song song, các phép gán blocking thực thi đồng thời, phép gán trước không chặn phép gán sau.
Hình 2: waveform của ví dụ 1
Ví dụ 2: Phép gán nonblocking
initial begin
  y <= #5 1'b1;
  y <= #3 1'b0;
end
initial fork
  x <= #5 1'b1;
  x <= #3 1'b0;
join
Trong ví dụ trên đây, x và y có waveform như nhau. Sau 3 đơn vị thời gian, x=y=0 và sau thêm 2 đơn vị thời gian nữa thì x=y=1. Trong khối tuần tự, phép gán nonblocking y <= #5 1'b1 không chặn việc thực thi của phép gán y <= #3 1'b0. Khi thực thi mô phỏng, giá trị 0 được ước lượng và việc gán được sắp xếp vào vùng NBA tại thời điểm T=3, giá trị 1 được ước lượng và việc gán được sắp xếp vào thời điểm T=5. Trong khối song song, việc thực thi cũng tương tự.
Hình 3: waveform của ví dụ 2
Hành vi của phép gán blocking phù hợp để mô tả mạch tổ hợp. Ngõ ra một mạch tổ hợp đáp ứng ngay lập tức theo sự thay đổi của ngõ vào, không tính độ trễ vật lý. Một phép gán blocking cập nhật vế trái ngay lập tức, không trì hoãn, khi giá trị vế phải thay đổi nên nó phù hợp cho việc mô tả mạch tổ hợp.
Hành vi của phép gán nonblocking phù hợp để mô tả mạch tuần tự dùng Latch hoặc Flip-Flop. Ngõ ra các mạch tuần tự, từ một Latch hoặc FF, thay đổi đồng thời tại mức/cạnh tích cực của clock. Một phép gán nonblocking xác định giá trị hiện tại của tất cả các ngõ vào trên các Latch và FF khi có mức/cạnh tích cực của clock. Sau đó, thực thi việc gán giá trị này đến ngõ ra của Latch và FF. Thời điểm thực hiện phép gán, sự thay đổi giá trị của ngõ vào không ảnh hưởng đến giá trị (đã được ước lượng xong) được gán cho ngõ ra. Như vậy, phép gán nonblocking phù hợp cho việc mô tả mạch tuần tự.
Trong mô tả RTL code khả tổng hợp, phép gán blocking và nonblocking được dùng trong khối tuần tự (procedural block) dùng always hoặc fucntion kết hợp cặp từ khóa begin/end. Việc dùng phép gán blocking hoặc nonblocking đều có thể tạo ra được mạch tổ hợp hoặc tuần tự như mong muốn. Tuy nhiên, phép gán blocking chỉ phù hợp để mô tả mạch tổ hợp và nonblocking chỉ phù hợp để mô tả mạch tuần tự. Phần sau đây sẽ phân tích chi tiết điều này.
3) Mô tả mạch tổ hợp
Ví dụ 3: Mô tả mạch tổ hợp bằng phép gán blocking (nên dùng)
always @* begin
  temp = in0 & in1;
  y = ~ temp;
end
Ví dụ 4: Mô tả mạch tổ hợp bằng phép gán nonblocking (không nên dùng)
always @* begin  temp <= in0 & in1;
  y <= ~ temp;
end
Ví dụ 5: Mô tả mạch tổ hợp bằng phép gán nonblocking trong always_comb (không nên dùng)
always_comb begin
  x <= in0 & in1;
  y <= ~x;
end
Tất cả các cách mô tả trên đây đều tổng hợp ra đúng mạch logic mong muốn nhưng hành vi thực thi mô phỏng thì không giống nhau.
Hình 3: Mạch logic của ví dụ 3, 4 và 5
Để kiểm chứng kết quả và hành vi thực thi mô phỏng, chúng ta thực hiện như sau:
  • Thêm task $display vào cuối khối always để kiểm tra kết quả các biến trong vùng ACTIVE và số lần vùng ACTIVE được thực thi.
  • Thêm task $strobe vào cuối khối always để kiểm tra kết quả các biến trong vùng POSTPONED, Đây là thời điểm kết thúc quá trình mô phỏng trong 1 khe thời gian nên nó thể hiện kết quả cuối cùng của các biến trong một khe thời gian.
  • Thêm một khối initial để khởi tạo giá trị in0 = 0 tại thời điểm mô phỏng T=0.
  • Kiểm tra thông điệp hiển thị trên terminal hoặc cửa sổ transcript của trình mô phỏng. Đồng thời, kiểm tra waveform của các tín hiệu.
Ví dụ 6: Code kiểm tra việc thực thi mô phỏng của phép gán blocking (nên dùng)
always @ (in0, in1) begin
  temp = in0 & in1;
  y = ~temp;
  $display("[%t] ACTIVE: in0 = %b, in1 = %b, temp = %b, y = %b", $time, in0, in1, temp, y);
  $strobe("[%t] POSTPONED: in0 = %b, in1 = %b, temp = %b, y = %b", $time, in0, in1, temp, y);
end
initial begin
  in0 = 0;
end
Đoạn code trên thực thi như sau:
  • Tại thời điểm T=0, tại vùng ACTIVE, in0 được gán bằng 0 và khởi động always.
  • x được gán bằng 0 vì vế phải in0 & in1 bằng 0 tại vùng ACTIVE
  • y được gán bằng ~temp=1 tại vùng ACTIVE
Kết quả hiển thị trên terminal cho thấy rằng khối always thực thi 1 lần và cho kết quả đúng ngay ở vùng ACTIVE.
# [ 0] ACTIVE: in0 = 0, in1 = x, temp = 0, y = 1
# [ 0] POSTPONED: in0 = 0, in1 = x, temp = 0, y = 1
Hình 4: waveform của ví dụ 6
Kết quả mô phỏng và hành vi thực thi tương ứng như một mạch tổ hợp. Nếu dùng phép gán blocking, cho dù danh sách độ nhạy được mô tả bằng cách nào thì kết quả mô phỏng vẫn chính xác. Các cách mô tả danh sách độ nhạy:
always @ (in0, in1)
hoặc:
always @ (*) //tương đương với always @ (in0, in1)
hoặc:
always @* //tương đương với always @ (in0, in1, temp)
hoặc:
always_comb //hỗ trợ bởi System Verilog
Tuy nhiên đối với phép gán nonblocking, sự thực thi mô phỏng và kết quả mô phỏng sẽ khác nhau tùy vào danh sách độ nhạy. Chúng ta cùng xem xét loạt ví dụ sau đây, các ví dụ này có mô tả logic code như nhau nhưng danh sách độ nhạy khác nhau.
Ví dụ 7: Dùng phép gán nonblocking với danh sách độ nhạy tường minh (không sử dụng)
always @ (in0, in1) begin
  temp <= in0 & in1;
  y <= ~temp;
  $display("[%t] in0 = %b, in1 = %b, temp = %b, y = %b", $time, in0, in1, temp, y);
  $strobe("[%t] POSTPONED: in0 = %b, in1 = %b, temp = %b, y = %b", $time, in0, in1, temp, y);
end
initial begin
  in0 = 0;
end
Kết quả mô phỏng của ví dụ trên là temp=0 và y=x (don’t care), một giá trị không xác định vì:
  • Tại thời điểm T=0, in0=0 kích hoạt khối always thực thi.
  • Tại vùng ACTIVE, in0 & in1 = 0 nhưng không được gán cho temp. ~temp là một giá trị không xác định nhưng không được gán cho y. Vì vậy temp và y đều là giá trị “x” (don’t care) khi kết thúc vùng ACTIVE. Hai phép gán giá trị cho temp và y được sắp xếp đến vùng NBA.
  • Tại vùng NBA, temp được gán bằng 0, y được gán một giá trị không xác định.
Trường hợp này, kết quả mô phỏng không đúng với mạch logic thực tế.
Hiển thị trên terminal cho thấy khối always thực thi 1 lần, giá trị của temp và y không được gán tại vùng ACTIVE. Kết thúc thời điểm T=0, vùng POSTPONED, giá trị của y thể hiện không đúng với hành vi của một mạch tổ hợp.
# [ 0] ACTIVE: in0 = 0, in1 = x, temp = x, y = x
# [ 0] POSTPONED: in0 = 0, in1 = x, temp = 0, y = x
Hình 5: waveform của ví dụ 7
Ví dụ 8: Dùng phép gán nonblocking với danh sách độ nhạy bao gồm biến trung gian (không sử dụng)
always @ (in0, in1, temp) begin
  temp <= in0 & in1;
  y <= ~temp;
  $display("[%t] in0 = %b, in1 = %b, temp = %b, y = %b", $time, in0, in1, temp, y);
  $strobe("[%t] POSTPONED: in0 = %b, in1 = %b, temp = %b, y = %b", $time, in0, in1, temp, y);
end
initial begin
  in0 = 0;
end
Kết quả mô phỏng của ví dụ trên là temp=0 và y=1 vì:
  • Tại thời điểm T=0, in0=0 kích hoạt khối always thực thi.
  • Tại vùng ACTIVE, in0 & in1 = 0 nhưng không được gán cho temp. ~temp là một giá trị không xác định nhưng không được gán cho y. Vì vậy temp và y đều là giá trị “x” (don’t care) khi kết thúc vùng ACTIVE. Hai phép gán giá trị cho temp và y được sắp xếp đến vùng NBA.
  • Tại vùng NBA, temp được gán bằng 0, y được gán một giá trị không xác định. Vì temp có trong danh sách độ nhạy nên nó kích hoạt khối always thực thi lần nữa.
  • Tại vùng ACTIVE, in0 & in1 = 0 nhưng không được gán cho temp, temp đang bằng 0. ~temp là 1 nhưng không được gán cho y. Vì vậy temp=0 và y=x khi kết thúc vùng ACTIVE. Hai phép gán giá trị cho temp và y được sắp xếp đến vùng NBA.
  • Tại vùng NBA, temp được gán bằng 0 (giữ nguyên giá trị trước đó), y được bằng 1.
Trường hợp này, kết quả cuối cùng đúng với mạch logic thực tế nhưng:
  • Cần thêm biến trung gian vào danh sách độ nhạy
  • Giá trị ngõ ra của logic tổ hợp đạt được giá trị mong muốn bằng cách thực thi always nhiều lần.
Hiển thị trên terminal cho thấy khối always thực thi 2 lần tại thời điểm T=0.
# [ 0] ACTIVE: in0 = 0, in1 = x, temp = x, y = x
# [ 0] ACTIVE: in0 = 0, in1 = x, temp = 0, y = x
# [ 0] POSTPONED: in0 = 0, in1 = x, temp = 0, y = 1
Hình 6: waveform của ví dụ 8
Danh sách độ nhạy ngầm hiểu @(*) tương đương @ (in0, in1) nên kết quả sẽ giống với ví dụ 7. Còn @* tương đương với @(in0, in1, temp) nên kết quả sẽ giống với ví dụ 8. Tuy nhiên, một số trình mô phỏng có thể đối xử với @(*) và @* theo một cách khác. Ví dụ như QuestaSim phiên bản 10.2c, @(*) và @* xem danh sách độ nhạy ngầm hiểu gồm cả các thay đổi bên trong always.
Ví dụ 9: Dùng phép gán nonblocking với danh sách độ nhạy @(*) (không sử dụng)
always @(*) begin
  temp <= in0 & in1;
  y <= ~temp;
  $display("[%t] in0 = %b, in1 = %b, temp = %b, y = %b", $time, in0, in1, temp, y);
  $strobe("[%t] POSTPONED: in0 = %b, in1 = %b, temp = %b, y = %b", $time, in0, in1, temp, y);
end
initial begin
  in0 = 0;
end
Thực thi mô phỏng trên QuestaSIM 10.2c như sau:
  • Tại thời điểm T=0, tại vùng ACTIVE, in0 được gán bằng 0 và khởi động always.
  • Tại vùng ACTIVE, in0 & in1 = 0 nhưng không được gán cho x. ~x là một giá trị không xác định nhưng không được gán cho y. Vì vậy x và y đều là giá trị “x” (don’t care) khi kết thúc vùng ACTIVE
  • Tại vùng NBA, x được gán 0, y được gán một giá trị không xác định. Khối always tiếp tục lặp lại vì x thay đổi giá trị.
  • Tại vùng ACTIVE, in0 & in1 = 0 nhưng không được gán cho x, x đang bằng 0. ~x là 1 nhưng không được gán cho y. Lúc này, x=0 và y là giá trị không xác định khi kết thúc vùng ACTIVE
  • Tại vùng NBA, x vẫn bằng 0, y được gán là 1. Khối always tiếp tục lặp lại vì y thay đổi giá trị. Chú ý, y đổi giá trị làm always thực thi lại một lần nữa
  • Tại vùng ACTIVE, in0 & in1 = 0 nhưng không được gán cho x, x đang bằng 0. ~x là 1 nhưng không được gán cho y, y đang bằng 1. Lúc này, x=0 và y=1 khi kết thúc vùng ACTIVE
Kết quả trên termial cho thấy khối always được lặp lại 3 lần tại thời điểm T=0:
# [ 0] ACTIVE: in0 = 0, in1 = x, temp = x, y = x
# [ 0] ACTIVE: in0 = 0, in1 = x, temp = 0, y = x
# [ 0] ACTIVE: in0 = 0, in1 = x, temp = 0, y = 1
# [ 0] POSTPONED: in0 = 0, in1 = x, temp = 0, y = 1
Trường hợp này, kết quả cuối cùng đúng với mạch logic thực tế nhưng:
  • Giá trị ngõ ra của logic tổ hợp đạt được giá trị mong muốn bằng cách thực thi always nhiều lần.
Kết quả mô phỏng với @(*) và @* là như nhau và như mô tả trên đây khi dùng QuestaSim 10.2c. Kết quả này có thể khác khi bạn dùng trình mô phỏng khác hoặc phiên bản khác của QuestaSim.
Nếu dùng System Verilog, bạn có thể dùng always_comb, và kết quả và sự thực thi giống với trường hợp @(in0, in1) trong cùng ví dụ đang phân tích. Chú ý, trong ví dụ này, always_comb và @(In0, in1) cho kết quả giống nhau. Điều này không có nghĩa là hai cách mô tả này tương đương nhau.
Với những phân tích trên đây, blocking hoàn toàn phù hợp để mô hình hóa một mạch tổ hợp trên cả phương diện tổng hợp và mô phỏng. Nonblocking không phù hợp để mô tả một mạch tổ hợp vì có thể kết quả mô phỏng có thể không chính xác hoặc tốn nhiều bước tính toán để đạt được kết quả cuối cùng.
4) Mô tả mạch tuần tự có FF
Đối với mạch tuần tự có FF, việc dùng phép gán blocking hay nonblocking đều có thể tổng hợp được logic mong muốn. Tuy nhiên, việc dùng phép gán blocking để mô tả mạch tuần tự sẽ có nhiều rủi ro nên phép gán này không được dùng khi mô tả mạch tuần tự.
Giả sử chúng ta cần mô tả một mạch tuần tự có 2 FF nối tiếp giữa ngõ vào in0 và ngõ ra y.
Ví dụ 10: Mô tả mạch tuần tuần tự bằng phép gán nonblocking (nên dùng)
always @ (posedge clk) begin
  temp <= in0;
  y <= temp;
end
hoặc:
always @ (posedge clk) begin
  temp <= in0;
  y <= temp;
end
hoặc:
always @ (posedge clk) begin
  temp <= in0;
end
always @ (posedge clk) begin
  y <= temp;
end

Ví dụ 11: Mô tả mạch tuần tự bằng phép gán blocking trong cùng một khối always (không nên dùng)
always @ (posedge clk) begin  y = temp;
  temp = in0;
end

Ví dụ 12: Mô tả mạch tuần tự bằng phép gán blocking trong hai khối always (không nên dùng)
always @ (posedge clk) begin
  temp = in0;
end
always @ (posedge clk) begin
  y = temp;
end
Kết quả tổng hợp của cả 3 ví dụ trên đây đều như nhau.
Hình 7: Mạch logic của ví dụ 10, 11 và 12
Đối với ví dụ 10, kết quả tổng hợp và mô phỏng luôn đúng và giống nhau cho cả 3 trường hợp. Điểm khác nhau duy nhất của 3 đoạn code là thứ tự thực hiện phép gán tại vùng NBA:
  • Thứ tự thực thi gán cho đoạn code đầu tiên
    • temp được gán
    • y được gán
  • Thứ tự thực thi gán cho đoạn code thứ 2
    • y được gán
    • temp được gán
  • Thứ tự gán cho đoạn code thứ 3 là tùy ý, tùy vào trình mô phỏng sắp xếp
Đối với ví dụ 11, kết quả tổng hợp và mô phỏng đúng như mong muốn. Tuy nhiên, điểm bất lợi là phải mô tả đúng theo thứ tự này. Theo đó, trong một chuỗi FF, FF của tầng sau phải được mô tả trước, hướng từ ngõ ra đến ngõ vào.
Hình 8: Thứ tự mô tả phép gán khi dùng blocking để mô tả mạch tuần tự
Ví dụ 11 được điều chỉnh lại như sau để có thể kiểm chứng kết quả mô phỏng:
  • Thêm task $display vào cuối khối always để kiểm tra kết quả các biến trong vùng ACTIVE và số lần vùng ACTIVE được thực thi.
  • Thêm task $strobe vào cuối khối always để kiểm tra kết quả các biến trong vùng POSTPONED, Đây là thời điểm kết thúc quá trình mô phỏng trong 1 khe thời gian nên nó thể hiện kết quả cuối cùng của các biến trong một khe thời gian.
  • Thêm một khối initial để khởi tạo giá trị in0 = 1 tại thời điểm mô phỏng T=0 và tạo xung clock clk.
  • Kiểm tra thông điệp hiển thị trên terminal hoặc cửa sổ transcript của trình mô phỏng. Đồng thời, kiểm tra waveform của các tín hiệu
Ví dụ 13: Code dùng để phỏng ví dụ 11
always @ (posedge clk) begin
  y = temp;
  temp = in0;
  $display("[%t] ACTIVE: in0 = %b, temp = %b, y = %b", $time, in0, temp, y);
  $strobe("[%t] POSTPONED: in0 = %b, temp = %b, y = %b", $time, in0, temp, y);
end
initial begin
  clk = 0;
  in0 = 1;
  forever #1 clk = ~clk;
end
Trình tự thực thi mô phỏng như sau:
  • Tại T=1, cạnh lên xung clock clk xuất hiện, khối always được kích hoạt
  • Giá trị temp được ước lượng là x và y được gán giá trị x trong vùng ACTIVE
  • Giá trị in0 được ước lượng là 1 và temp được gán là 1 trong vùng ACTIVE
  • Tại T=3, cạnh lên xung clock clk xuất hiện, khối always được kích hoạt
  • Giá trị temp được ước lượng là 1 và y được gán giá trị 1 trong vùng ACTIVE
  • Giá trị in0 được ước lượng là 1 và temp vẫn giữ giá trị 1 trong vùng ACTIVE
Kết quả hiện thị trên terminal cho thấy always được thực thi 1 lần tại thời điểm cạnh lên clk, T=1 và T=3.
# [ 1] ACTIVE: in0 = 1, temp = 1, y = x
# [ 1] POSTPONED: in0 = 1, temp = 1, y = x
# [ 3] ACTIVE: in0 = 1, temp = 1, y = 1
# [ 3] POSTPONED: in0 = 1, temp = 1, y = 1
Hình 9: waveform của ví dụ 13
Nếu thứ tự được đảo lại như ví dụ sau đây, cả kết quả tổng hợp và mô phỏng đều sai.
Ví dụ 14: Code minh họa việc tổng hợp và mô phỏng sai logic tuần tự khi dùng phép gán blocking
always @ (posedge clk) begin
  temp = in0;  y = temp;  $display("[%t] ACTIVE: in0 = %b, temp = %b, y = %b", $time, in0, temp, y);
  $strobe("[%t] POSTPONED: in0 = %b, temp = %b, y = %b", $time, in0, temp, y);
end
initial begin
  clk = 0;
  in0 = 1;
  forever #1 clk = ~clk;
end
Việc mô phỏng được thực thi như sau:
  • Tại T=1, cạnh lên xung clock clk xuất hiện, khối always được kích hoạt
  • Giá trị in0 được ước lượng là 1 và temp được gán là 1 trong vùng ACTIVE
  • Giá trị temp được ước lượng là 1 và y được gán giá trị 1 trong vùng ACTIVE
Hình 10: waveform của ví dụ 14
Khi tổng hợp, chỉ một FF được tạo ra giữa in0 và y.
Hình 11: Mạch logic của ví dụ 14
Đối với ví dụ 12, hai phép gán được mô tả trong 2 khối always độc lập. Kết quả tổng hợp sẽ có được 2 FF giữa in0 và y như mong muốn vì đây là hai khối always độc lập. Tuy nhiên, việc mô phỏng RTL code thì có vấn đề.
Ví dụ 14: Code minh họa việc mô phỏng sai logic tuần tự khi dùng phép gán blocking như ví dụ 12
always @ (posedge clk) begin
  temp = in0;
  $display("[%t][temp] ACTIVE: in0 = %b, temp = %b, y = %b", $time, in0, temp, y);
  $strobe("[%t][temp] POSTPONED: in0 = %b, temp = %b, y = %b", $time, in0, temp, y);
end
always @ (posedge clk) begin
  y = temp;
  $display("[%t][y] ACTIVE: in0 = %b, temp = %b, y = %b", $time, in0, temp, y);
end
initial begin
  clk = 0;
  in0 = 1;
  forever #1 clk = ~clk;
end
Kết quả mô phỏng là không thể xác định do chạy đua (race condition) xảy ra giữa hai khối always. Trường hợp này, thứ tự thực thi hai phép gán trong vùng ACTIVE không thể xác định. Thứ tự này phụ thuộc trình mô phỏng sắp xếp vì hai phép gán thuộc hai khối always độc lập. Kết quả là:
  • Nếu temp = in0 được thực thi trước thì kết quả giống như giữa in0 và y chỉ có 1 FF. Trên terminal, ngay thời điểm T=1, cạnh lên đầu tiên của clk, y=temp=1:
# [ 1][temp] ACTIVE: in0 = 1, temp = 1, y = x
# [ 1][y] ACTIVE: in0 = 1, temp = 1, y = 1
# [ 1][temp] POSTPONED: in0 = 1, temp = 1, y = 1
  • Nếu y = temp được thực thi trước thì kết quả giống như giữa in0 và y có 2 FF. Trên terminal, T=1, cạnh lên clk thứ 1, chỉ temp=in0=1; đến cạnh lên clk tiếp theo, T=3, y=temp=1:
# [ 1][y] ACTIVE: in0 = 1, temp = x, y = x
# [ 1][temp] ACTIVE: in0 = 1, temp = 1, y = x
# [ 1][temp] POSTPONED: in0 = 1, temp = 1, y = x
# [ 3][y] ACTIVE: in0 = 1, temp = 1, y = 1
# [ 3][temp] ACTIVE: in0 = 1, temp = 1, y = 1
# [ 3][temp] POSTPONED: in0 = 1, temp = 1, y = 1
Trên QuestaSim, bạn có thể đảo thứ tự code của hai khối always để thấy được điều này. Chú ý, theo quy chuẩn, thứ tự của 2 khối always trong một module không quyết định thứ tự thực thi của chúng. Thứ tự thực thi của chúng hoàn toàn do trình mô phỏng quyết định. Ở đây, có thể QuestaSim đang thực thự theo trình tự xuất hiện trong code nên bạn có thể dùng cách đảo vị trí khối always để thử nghiệm. 
Với nhưng ví dụ đã nêu, blocking sẽ không được dùng để mô tả mạch tuần tự vì nó có thể làm cho kết quả tổng hợp và mô phỏng sai với mong muốn. Nonblocking phù hợp để mô hình hóa mạch tuần tự vì nó cho kết quả tổng hợp và mô phỏng như nhau trong tất cả các trường hợp mô tả RTL code.


Câu hỏi 4 (2020.02.02):
Liệt kê các kỹ thuật có thể áp dụng để đồng bộ một tín hiệu nhiều bit (hai bit trở lên) giữa hai miền clock bất đồng bộ? Hãy nêu rõ vấn đề gặp phải, lý do áp dụng và ưu nhược điểm của kỹ thuật bạn đã nói nếu có thể.

Trả lời:
Tùy vào trường hợp cụ thể, một trong các phương pháp đồng bộ sau đây có thể được áp dụng để đồng bộ tín hiệu nhiều bit:
  • Sử dụng logic kiểm tra giá trị tín hiệu nhận được để đảm bảo nhận được giá trị đúng
  • Sử dụng tín hiệu cho phép (enable) để xác định thời điểm giá trị hợp lệ
  • Sử dụng cơ chế chuyển đổi giá trị thay đổi nhiều bit thành giá trị chỉ thay đổi 1 bit
  • Sử dụng giao thức bắt tay giữa hai miền clock để nhận biết khi nào giá trị được truyền và khi nào giá trị đã được nhận. Có hai loại giao thức bắt tay phổ biến là:
    • Bắt tay 4 pha (4-phase handshake)
    • Bắt tay 2 pha (2-phase handshake)
  • Sử dụng bộ nhớ đệm giữa hai miền như FIFO, RAM, ...
Chi tiết về các kỹ thuật đồng bộ, ưu điểm, nhược điểm và phạm vi ứng dụng được trình bày ở đây.



Câu hỏi 5 (2020.02.04):
Các phương pháp thiết kế giúp giảm công suất tiêu thụ (low power) trong mạch số mà bạn biết?

Trả lời:
Một số phương pháp thiết kế giúp giảm công suất tiêu thụ:

  • Phân chia miền clock (multi clock domain)
  • Hiệu chỉnh tần số clock (clock frequency scaling)
  • Tối ưu tài nguyên thiết kế (resource optimization)
  • Clock gating
  • Power gating
  • Phân chia miền điện áp (multi voltage hoặc multi power domain)
  • Sử dụng đa mức điện áp ngưỡng (multi threshold)

Chi tiết được mô tả trong bài viết "Các kỹ thuật thiết kế giúp giảm công suất tiêu thụ"



Lịch sử cập nhật:
2020.01.12 - Hoàn thành câu 1
2020.01.15 - Thêm chú thích về việc không dùng mạch tuần tự bất đồng bộ
2020.01.15 - Trả lời câu 2
2020.01.17 - Thêm mô tả unique/priority case cho câu 1
2020.01.19 - Trả lời câu 3
2020.02.02 - Trả lời câu 4
2020.03.01 - Trả lời câu 5

Tài liệu tham khảo:
[1] IEEE Computer Society and the IEEE Standards Association Corporate Advisory Group; IEEE Standard for SystemVerilog— Unified Hardware Design, Specification, and Verification Language; IEEE Std 1800™-2017; 6 December 2017
[2] Clifford E. Cummings, Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!; Sunburst Design, Inc; SNUG-2000