Tuesday, October 22, 2019

[Design][Bài 2] Tìm hiểu về clock crossing domain (Một số ví dụ vi phạm trong thiết kế mạch đồng bộ 2 FF)

Tác giả: TrongTran
Ngày: 22/10/2019
Tài liệu tham khảo: https://tel.archives-ouvertes.fr/tel-01053729/file/Distributed_clock_generator_for_synchronous_SoC_using_ADPLL_network.pdf
và các nguồn tư liệu từ internet,

Tham khảo một số hình ảnh waveform từ Wikipedia.


Hi, Lại là mình đây.

Hôm nay mình tiếp tục nói về các lỗi trong thiết kế mạch bất đồng bộ. Phần hôm nay tập trung vào tín hiệu data bất đồng bộ (không phải tín hiệu control nhé, tín hiệu control sẽ được nói trong bài tiếp theo).


Trường hợp 1

Đầu tiên và cơ bản nhất đó là lỗi không dùng mạch đồng bộ cho các tín hiệu bất đồng bộ.

Điều này sẽ gây ra hiện tượng bất ổn định hay còn gọi là metastabilities. 




Trong hình trên, nếu tín hiệu w1 thay đổi (0->1 hoặc 1->0) trong khoảng từ hold time hoặc setup time của clock c2 thì lúc này hiện tượng metastabilities xảy ra.


Trạng thái metastabilities là trạng thái data không xác định được giá trị. Phải mất một khoảng thời gian để đạt trạng thái ổn định.

(Ảnh nguồn : http://hardwarebee.com/metastability-in-fpgas/)

Trong hình bên trên chúng ta có thể thấy tín hiệu Din thay đổi ngay trong khoảng thời gian từ setup time đến hold time của clock clk2. Tuy nhiên tại đây Din không xác định giá trị là 0 hay 1 (Vì độ nghiên của cạnh data). Từ đó FF đầu tiên chạy với CLK2 không thể bắt chính xác giá trị từ Din và trạng thái bất ổn định xảy ra. Ta dùng 2 FF nhầm loại bỏ trạng thái này vì hầu như sau 1 chu kì xung clock CLK2,  tín hiệu ngõ ra Ds đã ổn định và FF thứ hai chạy với clock CLK2 đã bắt đúng giá trị ổn định đó.

Thời gian để tín hiệu trở nên ổn định. Nghĩa là từ khi xảy ra metastabilities đến khi đạt trạng thái 0 hoặc 1 gọi là closure time (Có thể gọi là metastable phase).

Close sure time được định nghĩa như bên dưới:


Clock cycle ≧ Closure time + Tsetup + Tskew + Tjitter

         ↑ ↑       ↑

       FF thứ nhất  FF thứ hai  clock properties

Như vậy MTBF sẽ phụ thuộc vào 3 yếu tố:

  • Tần số clock C1, C2.
  • Đặc điểm thư viện flip flop.
  • Closure time. 

Với một MTBF cho trước và thời gian closure time cho trước ta có thể lựa chọn thư viện cho FF đầu tiên trong mạch 2 FF phù hợp để có được design mong muốn.

Thế nên chúng ta mới có khái niệm meta FF, đó là các flip flop có tuổi thọ cao , bền và có các thông số phù hợp với MTBF và closure time cho trước.

Nếu điều kiện trên không thoả, nghĩa là chúng ta không thể chọn được FF đầu tiên phù hợp với mục đích ban đầu đưa ra thì có 2 cách giải quyết:

  • Tăng số tầng FF đến 3 FF hoặc 4 FF. Tuy nhiên, cách này sẽ làm tăng latency của hệ thống.
  • Trường hợp nếu tăng latency không được chấp nhận thì còn một cách là sử dụng bộ đồng bộ hoá pha.

Cái này thì mình chịu nè. Mình chưa biết đồng bộ khoá pha là gì. Chắc sẽ bổ sung sau. 

Một trường hợp khác cũng gây ra lỗi tương tự đó là fanout tín hiệu giữa 2 mạch 2FF như hình dưới:


Hoặc:



Tín hiệu metastabilities không nên đưa vào các mạch logic vì sẽ gây ra sai fucntion cũng như ảnh hưởng đến hệ thống.

Trường hợp 2

Lỗi thứ hai liên quan đến việc chèn các logic trước mạch async 2FF như hình bên dưới: 



Việc này sẽ gây ra glitch. Nghĩa là giả sử có sự delay giữa hai wire w1 và w2 như trên hình (Nguyên nhân có thể do dây dẫn, do combinational logic, ...). Thì lúc này glitch sẽ xuất hiện tại w3.
Các bạn tham khảo glitch như hình dưới:

Mình lấy ví dụ combinational logic là cổng AND.

Lúc này Glitch sẽ xuất hiện như sau:



Dữ liệu đúng phải là:
Tại xung clock lên X, tín hiệu w1 sẽ từ 1->0 và w2 sẽ từ 0->1. Lúc này kết qua w3 phải là = 0 vì 1 & 0 = 0. Tuy nhiên vì w1 vì một lí do nào đó bị delay như hình trên lúc này w3 sẽ có 1 khoảng 1&1 = 1. Hình trên clock C2 không bắt được glitch nên đầu ra không sao. Tuy nhiên, trường hợp glitch bị ngay trong khoảng thời gian từ setup time đến hold time của clock C2 thì glitch sẽ được capture và đầu ra sẽ bằng 1 trong 1 chu kì chứ không phải là 0 như kết quả ban đầu.

Vậy trong mạch đồng bộ, glitches có xuất hiện không. Câu trả lời là có. Tuy nhiên thời gian glitch tồn tại nhỏ và khi data truyền đến chu kì tiếp theo của clock (Cạnh lên clock tiếp theo) thì nó sẽ không capture được glitch để transfer. Ý mình là thời gian glitch kết thúc luôn sớm hơn thời gian bắt đầu một chu kì clock mới (data lúc này mới được capture).  Còn nếu delay quá nhiều dẫn đến glitch ảnh hưởng đến cạnh clock lên tiếp theo thì cái này STA sẽ control.

Glitches trở nên sai trong mạch bất đồng bộ trường hợp khi glitch vừa lên lúc này đúng ngay khoảng thời gian từ setup time hoặc hold time của clock phía nhận. Và data được transfer đi không đúng như chúng ta mong muốn.

Trường hợp 3

Trường hợp một output từ khối A bạn muốn nối đến nhiều input của khối B. Bạn phân nhánh nó tại khối A và dùng nhiều mạch đồng bộ cho từng nhánh đó. Điều đó có thể dẫn đến sự khác nhau về giá trị giữa các nhánh đó. 



Nguyên nhân là sau FF đầu , tín hiệu chuyển sang trạng thái metastabilities và lúc này FF 2 sẽ có thể bắt tín hiệu khác nhau là 0 hoặc 1. Dẫn đến tín hiệu ngõ ra có thể bị shift. Nghĩa là tín hiệu ngõ ra giữa out_2 và out_3 sẽ không giống nhau.

Hình dưới biểu thị data tại ngõ ra đã bị shift và không giống nhau.




Hãy dùng mạch 2 FF để đồng bộ tín hiệu output trước sau đó hãy phân nhánh nó vào từng input hợp lí. Điều này giúp giảm gate size của chip vì ta không cần đồng bộ cho từng nhánh mà chỉ đồng bộ 1 tín hiệu rồi phân nhánh nó sau khi đã đồng bộ.

Trường hợp 4

Phân nhánh data sau FF thứ nhất trong 2 FF. lúc này STA check không thể kiểm tra timing cho path này và sự delay khác nhau của các line này có thể gây ra việc shift data trong output của FF thứ 2.


Hiện tượng trong trường hợp này khá giống như trường hợp 3 bên trên.

Trường hợp 5

Data có thể bị lost nếu như độ rộng của xung data nhỏ hơn 1,5 lần chu kì clock nhận.

Mình nghĩ cái này khá là cơ bản và bạn nào cũng có thể biết. Có nhiều tài liệu nói về vấn đề này. Một số tài liệu ghi là xung data phải lớn hơn 2 lần. Tuy nhiên mình nghĩ 1,5 lần là đủ rồi.

Bạn thử nghĩ nếu xung data quá nhỏ. Lỗi hay gặp là transfer từ clock nhanh sang clock chậm. Data toggle sau khi cạnh clock lên và trước cạnh clock lên tiếp theo của clock C2 thì FF thứ 2 sẽ không capture được. Đây là một lỗi tưởng đơn giản nhưng rất nguy hiểm đấy nhé.

Hãy cẩn trọng với confirmation cho các path như thế này.

Trường hợp 6

Có lẽ đây là trường hợp cuối cùng trong phạm vi hiểu biết của mình về lỗi trong mạch đồng bộ tín hiệu data.



Trong 1 bus nhiều tín hiệu. Chúng ta không được sử dụng mạch đồng bộ cho từng tín hiệu riêng lẽ vì nhu vậy data trong bus sẽ bị thay đổi.

Với một bus chúng ta sẽ có mạch đồng bộ riêng để nhầm tránh sự sai biệt này.

Hôm nay viết dài rồi. Chắc bữa khác mình bổ sung hi vọng nó giúp bạn có cái nhìn tốt hơn về vấn đề đồng bộ mạch async. Nếu có gì sai sót vui lòng gửi comment hoặc gửi mail góp ý cho mình : Trongk10@gmail.com 

Sunday, October 20, 2019

[Design] Tìm hiểu về round robin arbiter

Tác giả: TrongTran
Ngày: 20/10/2019
Tài liệu tham khảo: https://sinhvientot.net/giai-thuat-dieu-phoi-round-robin-rr/
và các nguồn tư liệu từ internet.



Tình hình là mình đang gặp một issue trong quá trình verification và nó liên quan đến độ ưu tiên của khối arbiter.

Mình thấy nó khá là hay nên đã nghiên cứu và record lại. Mình không viết các bài liên tục vì mình không đủ thời gian. Khi mình gặp issue nào đó mình sẽ nhảy vào nghiên cứu và ghi lại như là một note. Dĩ nhiên note thì sẽ có sai sót. Hi vọng mọi người có thể học hỏi và góp ý cho mình.


1. Sơ lược về arbiter.

Mình lấy một ví dụ đơn giản trong cuộc sống hằng ngày. Có 3 người cần đi mua hàng tại một siêu thị.

Người thứ nhất (A) cần mua 20 gói mì, người thứ 2 (B) cần mua 2 gói mì, người thứ 3 (C) cần mua 4 gói mì.

Như vậy nhiệm vụ của một bộ arbiter cũng giống như cách mà chúng ta giải quyết bài toán này để đảm bảo sự công bằng cho cả 3 người. Cả 3 người đều được mua hàng nhưng không phải đợi quá lâu.

Trở lại vấn đề, Bây giờ ta xem người A như master A, tương tự cho người B, C như master B, C.

Có 3 khả năng xảy ra:

  • Master A gửi request đến slave trước. Lúc này chưa có master nào được gửi. Thì mặc nhiên A sẽ được đi qua.Đây là trường hợp các master gửi đến slave không cùng 1 lúc. Như vậy master nào gửi trước sẽ được qua trước, master nào gửi sau sẽ đi sau.
  • Các master đến cùng 1 lúc và không xét độ ưu tiên giữa các master. Khi đó ta sẽ gán 1 thông số biểu thị thứ tự của từng master để quyết định master nào được gửi trước và master nào gửi sau trong trường hợp cùng đến 1 lúc.
  • Các master đến cùng 1 lúc và có tính đến độ ưu tiên. Lúc này, Các master có độ ưu tiên cao hơn sẽ chiếm lấy quyền access đến slave bất kể khi nào nó access đến bộ arbiter.
Trên thực tế, trong một design, không phải lúc nào master cũng được xem xét có độ ưu tiên ngang nhau. Ví dụ các IP về xử lí ảnh sẽ được ưu tiên transfer data trước. Điều này dẫn đến người design cần thiết lập độ ưu tiên cho các master này.

Mình tóm lại, Bộ arbiter có chức năng xem xét để quyết định xem tại một thời điểm master nào sẽ được access vào slave đồng thời điều khiển BW cho từng master đó.


2. Round robin arbiter. 

Có một số RTL code trên mạng viết cho bộ arbiter, Mình thấy đa số chỉ dùng cấu trúc đơn giản:

if

else


Giả sử trong 1 chip thực tế khi chúng ta sử dụng cấu trúc như trên. Nghĩa là nếu có từ 2 master access liên tục cùng lúc đến cùng 1 slave thì master nào nằm trong vòng if trước sẽ được transfer liên tục và master còn lại cần phải đợi master kia transfer hết data mới bắt đầu transfer.

Điều này làm giảm performance của chip vì như vậy sẽ làm giảm khả năng thực hiện nhiều function cùng 1 lúc của 1 chip.

Hầu hết các thiết kế ngày nay, các nhà design đều sử dụng bộ round robin arbiter.
Một số ưu điểm của bộ này đó là cân bằng được bandwidth và phân bổ tài nguyên hợp lí giữa các master.

Trong 1 bộ round robin arbiter có thể có 2 mode hoạt động chính:

  • Điều khiển BW cho từng master. Nghĩa là mỗi master sẽ được thiết lập một độ ưu tiên. Các master đòi hỏi BW cao hơn hoặc tốc độ xử lí nhanh hơn thì sẽ cần nhiều BW hơn.
  • Không điều khiển BW cho các master. Đây là chế độ mặc định. Các master sẽ được phân bổ round robin với không thiết lập ưu tiên. Nghĩa là master nào cũng được cung cấp 1 BW như nhau.
Trong ví dụ phần trên về 3 người A, B, C đi mua mì, các bạn có thể thao khảo sự phân bổ round robin như hình dưới:




Mỗi người sẽ mua được tối đa 3 gói mì. A mua trước và mua 3 gói, khi đó A nhường cho B để B mua. Lúc này A muốn mua tiếp phải đợi B mua xong 3 gói rồi tới C sau đó mới tới A.

Ta có sơ đồ như hình bên dưới:



Trong hình trên sp đại diện cho số request được gửi tối đa trong một lần gửi (Hay còn gọi là số quantum). req là tổng số request của master còn lại chưa được gửi đến slave.

Mô hình một bộ arbiter như bên dưới:




Khối mux ở đây là để quyết định sẽ cho transaction từ master nào được đi qua.

Combinational logic là nhằm kiểm tra xem địa chỉ bạn đi vào có hợp lệ không.

Module control sẽ điều khiển chế độ hoạt động của bộ arbiter. Có 2 chế độ hoạt động như bên dưới:

Không control BW.

Control BW cho một số master.

Như vậy, Làm thế nào module control có thể điều khiển BW cho các master đi qua bộ arbiter. Đó là nhờ vào cập tín hiệu VALID/READY. Nghĩa là nhờ vào cơ chế bắt tay. Tín hiệu valid thì dĩ nhiên được gửi từ master và chúng ta không thể control được, như vậy ta sẽ control tín hiệu READY. Nghĩa là điều khiển việc khi nào slave sẽ sẵn sàng nhận tín hiệu từ master.

Trong chúng ta ai cũng biết, BW tỉ lệ thuận với số transaction giao dịch thành công trong một đơn vị thời gian. Vì vậy việc điều khiển tín hiệu READY sẽ giúp chúng ta tính toán được chính xác BW đi qua bộ arbiter.

Cơ chế round robin :
Mình lười viết quá nên mình lấy một hình trong 1 trang web về round robin cho CPU. Nó tương tự cho arbiter. Các bạn tham khảo bài đầy đủ ở đây ha:

https://sinhvientot.net/giai-thuat-dieu-phoi-round-robin-rr/



Hình trên mô tả round robin không có độ ưu tiên. P1 vào trước. Lúc này P1 sẽ sử dụng CPU. Ta xét số quantum = 4. Quantum là số thời gian tối đa 1 tiến trình dùng CPU liên tục. Trong bộ arbiter thì đó là số transaction được transfer trong 1 lượt từ 1 master qua bộ arbiter. Nếu vượt quá nó sẽ chặn lại và cho master kế tiếp qua.

Trường hợp nếu P1 không vào trước mà cả P1, P2, P3 vào cùng lúc thì sao. Chúng ta sẽ có 1 trọng số để quyết định tiến trình nào được thực hiện trước.


Trường hợp hình trên giống như xe cứu hoả vậy. bộ arbiter là đèn đỏ. Khi xe qua đèn đỏ thì mọi người phải dừng lại nhường cho đến khi xe cứu hoả qua xong. Đây là bộ arbiter có xét đến tính ưu tiên.

Bài tiếp theo mình sẽ design 1 arbiter nho nhỏ để các bạn tham khảo.





Tuesday, October 15, 2019

[Design][Bài 1] Tìm hiểu về clock domain crossing (CDC)

Tác giả: TrongTran
Ngày: 15/10/2019
Tài liệu tham khảo: https://tel.archives-ouvertes.fr/tel-01053729/file/Distributed_clock_generator_for_synchronous_SoC_using_ADPLL_network.pdf
và các nguồn tư liệu từ internet.


1. Tổng quan. 

Hiện nay , Để giảm thiểu thời gian design thì các nhà sản xuất chip trên thế giới thường sử dụng các IP sẵn có. Các IP này là những IP common. Nghĩa là chúng thực hiện một chức năng riêng biệt nào đó mà hầu hết mọi chip đều sử dụng với mục đích giống nhau. 

Ví dụ: IP chuyển đổi chuẩn PCIe và chuẩn AMBA của ARM, ipmmu …. 

Với việc sử dụng các IP có sẵn người design có thể tập trung vào những phần khác của design quan trọng hơn. Đó là các phần tạo nên sự khác biệt giữa chip của họ so với những chip khác trên thị trường. 

Với việc sử dụng các IP có sẵn, Một vấn đề khác đặt ra đó là các IP có sẳn chỉ chạy với một số tần số nhất định. Trong trường hợp chip của bạn hoạt động với tần số trùng với tần số của các IP này, điều đó khá là tốt. Tuy nhiên, Nếu IP của bạn hoạt động khác tần số với các IP này thì vô tình thiết kế của bạn sẽ có nhiều vùng clock trong một chip. Điều này dẫn đến việc design cho mạch đồng bộ giữa các miền clock async trở nên phức tạp. Bên cạnh đó nhằm tăng performance của chip, chúng ta cũng cần thiết kế các IP chạy với clock khác nhau.




Hình trên miêu tả tổng quan về các khối IP sử dụng các clock khác nhau. Đồng bộ trong từng khối nhỏ và bất đồng bộ giữa các khối với nhau.

Mạch đồng bộ các tín hiệu clock sẽ có thể được đặt tại 3 vị trí:

Trong IP nhận hoặc gửi: Thường được sử dùng nhiều trong các design trước đây.Điều này đòi hỏi IP nhận/gửi cần input clock của IP gửi/nhận để cung cấp clock cho các mạch 2 FF đồng bộ. Lưu ý ở đây là trong 1 chuẩn giao tiếp sẽ luôn có việc trao đổi tín hiệu hai chiều (Ví dụ chuẩn AMBA thì kênh AR/AW/W từ master qua slave, kênh R/B từ slave đến master) do đó dù đặt mạch async trong IP gửi hoặc nhận thì cũng phải cần input clock của khối còn lại. Mình ví dụ hình bên dưới block A là khối gửi (được thiết kế đồng bộ) nhưng phải input clock clk2 để cấp clock cho mạch đồng bộ trước khi out data ra ngoài. Đồng thời sử dụng clock clk1 để đồng bộ các tín hiệu feedback. Việc này khá rườm rà và khó quản lí. 




Trong cả IP gửi và nhận: Như mình nói bên trên, trong 1 chuẩn giao tiếp sẽ luôn có việc trao đổi tín hiệu hai chiều. Giả sử ta có 2 khối A và B. Một số tín hiệu từ A gửi sang B sẽ có mạch đồng bộ các tín hiệu này tại B (Khối nhận). Một số tín hiệu từ B gửi sang A sẽ có mạch đồng bộ các tín hiệu trong A (Khối nhận). Việc này thật sự rất khó control. Tốn nhiều tài nguyên và design sẽ trở nên phức tạp.

Trong Top module: Tức là người design sẽ thiết kế một cầu chỉ có chức năng đồng bộ 2 clock. Cầu này sẽ chứa các mạch 2FF để đồng bộ các tín hiệu async. Clock vào là 2 clock gồm clock IP nhận và clock IP gửi. 


Cách này đang là xu hướng design trên thế giới. Nó giúp việc verify trở nên dễ dàng, Người build top dễ xác định và chạy kiểm tra timing cho design cũng như các lỗi khác.

Ngoài ra, với cách design bên trên thì ta có thể merge các cầu async lại thành 1 cầu async duy nhất để dễ quản lí và làm giảm area cũng như sự phức tạp của chip. Lưu ý nếu chip quá to thì chúng ta không nên gộp lại vì chúng ta sẽ không thể met timing.
Điều này còn giúp ta quản lí skew của clock theo một khối riêng biệt như hình dưới:





2. Phân loại.

Dựa vào kiểu tín hiệu async, Ta có thể chia thành 2 loại:

  • Mạch đồng bộ cho tín hiệu control.

  • Mạch đồng bộ cho tín hiệu data.

Dựa vào cách xác định 2 tín hiệu async, Ta có thể chia thành 3 loại tín hiệu cơ bản đó là:

  • Tín hiệu data truyền từ bên ngoài vào mà không rõ timing. Thường đây là các tín hiệu bên ngoài chip chẳng hạn data truyền từ router, từ PCIe…

  • Tín hiệu truyền giữa hai miền clock khác nhau bao gồm: Khác về tần số, Khác pha, Tín hiệu chạy với clock nguồn và tín hiệu chạy với clock output của bộ PLL.

  • Tín hiệu reset bên ngoài vào mà không rõ timing.
Ngoài ra nếu hai tín hiệu chạy với clock cùng tần số, cùng pha nhưng khác clock source, thì chúng vẫn được xem là async với nhau.

Hình bên dưới cho thấy clock source và clock đi qua khối PLL là khác nhau:


3. Giải pháp.

Để đồng bộ các tín hiệu async, trong thiết kế ngày nay ta hay dùng mạch 2 FF. Dĩ nhiên có nhiều mạch sync mà không dùng 2FF nhưng ở đây mình chỉ biết mạch này (Mình sẽ bổ sung sau).Với mạch này ta có một thông số gọi là MTBF (mean time between failure) để đặc trưng cho thời gian giữa hai lần fail liên tiếp.

Failure rate = 1/MTBF (cho một mạch async) và Failure rate của toàn bộ design sẽ bằng tổng của tất cả các Failure rate trong thiết kế. Đơn vị đo lường của MTBF là đơn vị thời gian. Thường thì thời gian giữa hai lần fail rất lớn (MTBF từ 100 – 1000 năm).

Trong bài tiếp theo mình sẽ phân tích về một số mạch async có thể gây ra lỗi trong quá trình design.

Nếu bài của mình có sai sót gì mong nhận được sự góp ý nhiệt tình từ mọi người. Vì bản thân không phải là chuyên gia, cũng mài mò học hỏi nên không tránh khỏi sự sai sót.

[Design][Thiết kế CPU 32 bit] [Bài 4] Tìm hiểu datapath và cấu trúc của các câu lệnh nhóm I-format (ADDI, SUBI, ORI, XORI,SLLI…) trong thiết kế RISC-V

Tác giả: TrongTran
Ngày: 14/10/2019

Tài liệu tham khảo: www-inst.eecs.berkeley.edu/~cs61c/fa18/img/riscvcard.pdf và tài liệu học tập từ trường đại học Bách Khoa TP.HCM (Bộ môn Điện-Điện tử).

Đây là một bài nằm trong chuỗi bài hướng dẫn thiết kế 1 CPU RISC-V 32 bit.

Như các bạn đã biết, trong bài trước mình đã hướng dẫn chi tiết về datapath và cấu trúc của các câu lệnh nhóm R-format (http://trongtranrvc.blogspot.com/2019/09/designthiet-ke-cpu-32-bit-bai-2-tim_17.html).

Bài này, Mình tiếp tục hướng dẫn về datapath và cấu trúc nhóm lệnh I-format.

Các bạn lưu ý cấu trúc trong bài này chỉ dành riêng cho các câu lệnh “addi, subi, andi, ori ,xori, slli, srli, srai, slti”.
Các câu lệnh khác mình sẽ nói trong bài tiếp theo.

Đừng nhầm lẫn giữa các câu lệnh nhóm R-format trong bài 3 và nhóm I-format trong bài này nhé.

Dưới đây là bảng nhóm câu lệnh I-format:



Cú pháp nhóm câu lệnh I-format là:

                       instruction rd, rs1, A

                       Hoặc
                              instruction rd, rs1, rs2 (cho lệnh slli, srli, srai)

Trong đó “instruction” là các lệnh “addi, ori, xori….” trong bảng trên.

rd, rs1, rs2 : là các thanh ghi.

A là một số nguyên được biểu diễn bởi 12 bit.

Nhóm lệnh này có opcode là [6:0] = 0010011. Các lệnh I-format (trừ 3 lệnh SRAI, SRLI, SLLI) thực hiện lấy giá trị lưu ở thanh ghi rs1 và giá trị lưu ở imm[11:0] (được mở rộng dấu từ 12 bit thành 32 bit), thực hiện đưa vào khối ALU để tính toán. Kết quả được lưu vào thanh ghi rd trong khối REG.

Như trong bảng trên giá trị imm là một số 12 bit (Nghĩa là giá trị đưa vào nằm trong khoảng

[-(2^(n-1)): (2^(n-1)) – 1] = [-2048:2047]



Một điều đặc biệt trong bảng trên nữa là có 3 câu lệnh slli, srli, srai không dùng imm[11:0] mà dùng thành ghi.

Giá trị shamt được biểu diễn bởi 5 bit data biểu diễn cho các thanh ghi từ R0 đến R31.




Nhóm câu lệnh R-format (trong bài 3) và 3 lệnh slli, srli, srai sẽ sử dùng path màu xanh trong hình trên. Inst[24:20] là 5 bit biểu diễn cho thanh ghi rs2 (giá trị từ 0 -> 31 đi vào khối Reg, dựa vào tên thanh ghi, khối reg sẽ trích xuất giá trị 32 bit ra và đưa vào khối ALU để tính toán).

Các nhóm lệnh I-fromat còn lại không dùng path màu xanh mà dùng path màu tím.

12 bit data inst[31:20] được đưa vào khối imm. Khối này có chức năng đó là mở rông bit dấu từ 1 giá trị 12 bit sang một giá trị 32 bit data rồi đưa vào khối ALU để tiến hành tính toán.

Các khối còn lại thức hiện các bước tương tự như bài 3.

Mình tóm lại các bước thực hiện khối lệnh I-format trong RISC-V như sau:

  • Giá trị PC được đưa vào IMEM (Cũng có thể xem giá trị PC là ADDRESS, Và khối IMEM sẽ đưa ra giá trị lưu tại address đó). IMEM sẽ gửi ra một chuổi 32 bit. Chuỗi này mang một ý nghĩa nhất định là các câu lệnh theo mã nhị phân.

  • 32 bit out từ IMEM sẽ được phân tích theo các vị trí riêng và gửi vào khối REG, Khối IMM.
  • Khối IMM sẽ mở rộng bit dấu cho 12 bit data đưa vào thành 32 bit data và gửi sang khối ALU cho việc tính toán.

  • Khối ALU tính toán kết quả và gửi về khối REG nhằm lưu kết quả vào thanh ghi rd.

  • Lúc này PC = PC + 4. Để tiếp tục đọc câu lệnh tiếp theo.



Bên dưới là RTL cho khối IMM:



//Author: TrongTran


module imm (Imm_in, Imm_out, ImmSel);


input [24:0] Imm_in;

input [2:0] ImmSel;

output [31:0] Imm_out;

wire [31:0] Imm_out;



assign Imm_out = (ImmSel==3'b000)? {{21{Imm_in[24]}},Imm_in[23:13]}:

(ImmSel==3'b001)? {{21{Imm_in[24]}},Imm_in[23:18], Imm_in[4:0]}:

(ImmSel==3'b010)? {{20{Imm_in[24]}}, Imm_in[0], Imm_in[23:18],Imm_in[4:1], 1'b0} :

(ImmSel==3'b011)? {Imm_in[24:5], {12{1'b0}}} :

(ImmSel==3'b100)? {{21{Imm_in[24]}},Imm_in[23:13]} :

(ImmSel==3'b101)? {{13{Imm_in[24]}}, Imm_in[12:5], Imm_in[13], Imm_in[23:14],1'b0} : 32'h0000_0000;

endmodule




Code này mình đưa vào input 25 bit chứ không phải 12 bit như đề cập bên trên. Lí do là nó support cho những câu lệnh khác nữa.


ImmSel là tín hiệu control từ khối control. Tín hiệu này sẽ cho biết đây là câu lệnh gì và khối imm sẽ dựa vào đó để mở rộng bit cho đúng tuỳ vào loại câu lệnh.

Nếu bài của mình có sai sót gì mong nhận được sự góp ý nhiệt tình từ mọi người. Vì bản thân không phải là chuyên gia, cũng mài mò học hỏi nên không tránh khỏi sai sót.

Friday, October 11, 2019

[Unity][Game 3D] Cách tạo Skybox trong unity

Tài liệu tham khảo:  https://docs.unity3d.com/560/Documentation
Biên dịch, soạn thảo và bổ sung: TrongTran
Ngày: 11/10/2019

Hi, Mình đang loay hoay với 1 dự án game 3D và vấn đề đặt ra với mình là làm thế nào để tạo ra Skybox cho 1 scene.

Tìm mãi cũng thấy, Hôm này mình sẽ hướng dẫn bạn tạo một Skybox trong unity.





Bắt đầu nào.

Skybox là một khối lập phương 6 mặt được vẽ đằng sau tất cả đồ họa trong trò chơi. Dưới đây là các bước để tạo một Skybox trực tiếp trong Unity:


  • Tạo 6 kết cấu tương ứng với 6 mặt của Skybox (Các kết cấu này có thể là ảnh bầu trời) và đặt chúng vào thư mục Asset dự án của bạn.

  • Việc đầu tiên là bạn cần click vào từng cái kết cấu và quan sát trong cửa sổ inspector. Đối với mỗi kết cấu, bạn cần thay đổi chế độ “wrap mode” từ “Repeat” thành “claim”. Nếu bạn không làm điều này các cạnh của khối lập phương 6 mặt sẽ không khớp với nhau.



Tạo Material mới bằng cách chọn Assets-> create-> Material từ thanh menu.



Click vào material vừa tạo và quan sát trong cửa sổ inspector.
Chọn trình đơn thả xuống “Shader” ở trên cùng của inspector, chọn “Skybox/6  sided”.
Kéo thả 6 texture vào từng vị trí tương ứng của kết cấu trong material như hình bên dưới. 
Như vậy là ta đã có một Skybox rồi.
Bây giờ ta tìm hiểu cách import Skybox vào Terrain của chúng ta nhé.
Để gán skybox vào scene chúng ta cần thực hiện một số thao tác bên dưới:


  •      Chọn Window-> Lighting từ thanh menu.
  •      Trong cửa sổ xuất hiện, chọn tab “scene”.
  •      Kéo Vật liệu Skybox mới vào khe Skybox.


Tuesday, October 8, 2019

[UVM] Mô phỏng môi trường UVM đầy đủ thành phần chỉ sử dụng ngôn ngữ System Verilog (Không dùng thư viện uvm)



Tác giả: ThienPham , TrongTran.
Ngày: 08/10/2019


Có một anh bạn của mình đã hướng dẫn mình từ những ngày đầu mình bắt tay học system Verilog và uvm.

Đây là một môi trường có đầy đủ các thành phần trong một môi trường random test căn bản.
Tuy nhiên, trong bài này mình không sử dụng thư viện uvm mà sử dụng chỉ ngôn ngữ System Verilog.
Hi vọng bài viết này sẽ giúp cho những bạn vừa bắt tay vào nghiên cứu System Verilog và uvm hiểu rõ hơn.

Các bạn có thể tham khảo 1 môi trường uvm đơn giản hoàn chỉnh trong bài viết này:



Trong bài này mình xét 1 DUT như bên dưới:

module bit_adder ( clk, rst_n, in_a, in_b, carry_in, carry_out, sum );

input  clk;
input  rst_n;
input  in_a;
input  in_b;
input  carry_in;
output carry_out;
output sum;

wire   clk;
wire   rst_n;
wire   in_a;
wire   in_b;
wire   carry_in;
reg    carry_out;
reg    sum;

always @(posedge clk or negedge rst_n) begin
  if (rst_n == 1'b0) begin //reset is triggered
    carry_out = 0;
    sum       = 0;
  end else begin
    carry_out <= ( in_a & in_b ) | ( carry_in & ( in_a ^ in_b ));
    sum       <= in_a ^ in_b ^ carry_in;
  end
end

endmodule

Bên trên là bộ bit_adder. Nghĩa là cộng 2 phần tử 1 bit và biến nhớ 1 bit đầu vào lại với nhau.
Dưới đây là bộ multi_bit_adder dùng để cộng 2 phần tử nhiều 3 bit:

module multi_bit_adder (adder_if.ADDER multi_bit_adder_if);

wire carry_out_0;
wire carry_out_1;

bit_adder adder_0 (  .clk       (multi_bit_adder_if.clk)
                    ,.rst_n     (multi_bit_adder_if.adder.rst_n)
                    ,.in_a      (multi_bit_adder_if.adder.in_a[0])
                    ,.in_b      (multi_bit_adder_if.adder.in_b[0])
                    ,.carry_in  (multi_bit_adder_if.adder.carry_in)
                    ,.carry_out (carry_out_0)
                    ,.sum       (multi_bit_adder_if.sum[0]) );

bit_adder adder_1 (  .clk       (multi_bit_adder_if.clk)
                    ,.rst_n     (multi_bit_adder_if.adder.rst_n)
                    ,.in_a      (multi_bit_adder_if.adder.in_a[1])
                    ,.in_b      (multi_bit_adder_if.adder.in_b[1])
                    ,.carry_in  (carry_out_0)
                    ,.carry_out (carry_out_1)
                    ,.sum       (multi_bit_adder_if.sum[1]) );

bit_adder adder_2 (  .clk       (multi_bit_adder_if.clk)
                    ,.rst_n     (multi_bit_adder_if.adder.rst_n)
                    ,.in_a      (multi_bit_adder_if.adder.in_a[2])
                    ,.in_b      (multi_bit_adder_if.adder.in_b[2])
                    ,.carry_in  (carry_out_1)
                    ,.carry_out (multi_bit_adder_if.carry_out)
                    ,.sum       (multi_bit_adder_if.sum[2]) );

endmodule

Với đoạn code bên trên chúng ta đang tiến hành cộng 2 số 3 bit. Kết quả là 1 số 3 bit và 1 biến nhớ.

Các bạn có thắc mắc cấu trúc này không:
       adder_if.ADDER multi_bit_adder_if

Không có gì khó hiểu cả. Chúng ta đang tiến hành connect bộ “multi_bit_adder” và interface. Điều này giúp chúng ta giảm 1 thao tác để connect trong khối top module.

Khối interface:

interface adder_if (input wire clk);
  wire  [2:0]  in_a;
  wire  [2:0]  in_b;
  wire         carry_in;
  wire         rst_n;
  logic [2:0]  sum;
  logic        carry_out;

  clocking adder @ (posedge clk);
    input   in_a;
    input   in_b;
    input   carry_in;
    input   rst_n;
  endclocking

  clocking testbench @ (posedge clk);
    input   sum;
    input   carry_out;
    output  in_a;
    output  in_b;
    output  carry_in;
    output  rst_n;
  endclocking

  clocking assertion @ (posedge clk);
    input   sum;
    input   carry_out;
  endclocking

  modport ADDER     (clocking adder, input clk, output sum, output carry_out);
  modport TESTBENCH (clocking testbench, input clk);
  modport ASSERTION (clocking assertion
                     ,input clk
                     ,input rst_n
                     ,input in_a
                     ,input in_b
                     ,input carry_in);

endinterface

Khối này mình đã nói đến trong những bái trước đây các bạn có thể tham khảo lại nhé.

Khối transaction:

`ifndef ADDER_BASE_OBJECT_SV
`define ADDER_BASE_OBJECT_SV
class adder_base_object;
  rand bit [2:0] in_a;
  rand bit [2:0] in_b;
  rand bit carry_in;
  bit carry_out;
  bit [2:0] sum;
  bit rst_n = 1;

   constraint legal {
     in_a dist { [0  :  7 ] := 100 };
     in_b dist { [0  :  7 ] := 100 };
   }

endclass
`endif

Class adder_base_object tương tự như class transaction trong môi trường UVM.
Chức năng khối này là khai báo các input/output giao tiếp giữa khối sequence và DUT. Hay nói đúng hơn đó là các cổng input/output của DUT.
Với những biến input ta sẽ khai báo bởi các key word: “rand” kèm theo các constraint cho việc random đó. Còn với output bạn định nghĩa kiểu "bit".

Khối configuration:

class Config;
  integer a;
  integer b;
  constraint reasonable {
    a inside {[0:7]};
    b inside {[0:7]};
  }
endclass

Thật ra khối này không quan trọng và cũng không thật sự cần trong môi trường UVM. Khối config nhằm giúp cho người verifier dễ dàng chỉnh sữa các constraint hay thông số đầu vào của khối transaction mà không làm sữa đổi khối transaction. Với một môi trường UVM phức tạp thì bộ input rất lớn và có những constraint vô cùng phức tạp. Khi chúng ta cần check một case cụ thể ta thường sữa trong khối config.

Khối generator:

`ifndef ADDER_TXGEN_SV
`define ADDER_TXGEN_SV
class Generator;

  adder_base_object adder_object;
  mailbox gen2agt;

  function new(mailbox gen2agt);
//    this.gen2agt = new(); //if use this line. this mail is discarded.
    this.gen2agt = gen2agt;
  endfunction

  task run(int count);
    repeat (count) begin
      adder_object = new();
      assert(adder_object.randomize);
      gen2agt.put(adder_object);
    end
  endtask

endclass
`endif

Khối này nhằm mục đích tạo ra các tín hiệu random gửi vào DUT. Chúng ta có rất nhiều cách để tạo ra 1 tín hiệu random.
Mình lấy ví dụ trong môi trường UVM ta có:

  • uvm_do
  • uvm_do_with
  • uvm_do_on_with
  • ...

Ngoài ra còn có một số cách khác như là:

  • void'(std::randomize(a));


Dùng cập:

start_item(a);
assert (a.randomize ());

finish_item(a);

Trong trường hợp trên mình dùng câu lệnh:

  • assert(adder_object.randomize);


Khối driver:

`ifndef ADDER_DRIVER_SV
`define ADDER_DRIVER_SV
class Driver;
  adder_base_object adder_object;
  mailbox agt2drv;
  virtual adder_if.TESTBENCH test_port;

  function new(mailbox agt2drv, virtual adder_if.TESTBENCH test_port);
    this.agt2drv    = agt2drv;
    this.test_port  = test_port;
  endfunction

  task run(int count);
    repeat (count) begin
      agt2drv.get(adder_object);
      test_port.testbench.in_a     <= adder_object.in_a;
      test_port.testbench.in_b     <= adder_object.in_b;
      test_port.testbench.carry_in <= adder_object.carry_in;
      test_port.testbench.rst_n    <= adder_object.rst_n;
      @(posedge test_port.clk);
      @(posedge test_port.clk);
      @(posedge test_port.clk);
      @(negedge test_port.clk);
      $write("--------------------------- New Test Data----- --------------------\n");
      $write("@%4d   clk: %0d  rst: %0d       a: %0d  b: %0d  carry_in: %x  sum: %0d  carry_out: %x\n"
              , $time
              , test_port.clk
              , test_port.testbench.rst_n
              , test_port.testbench.in_a
              , test_port.testbench.in_b
              , test_port.testbench.carry_in
              , test_port.testbench.sum
              , test_port.testbench.carry_out);
    end
      @(posedge test_port.clk);
      @(posedge test_port.clk);
      @(posedge test_port.clk);
      @(negedge test_port.clk);
    $finish;
  endtask

endclass

`endif


Trong đoạn code trên, mình đơn giản là control các tín hiệu in_a, in_b, rst_n, carry_in từ khối generator bên trên để gửi vào DUT.
Số lần lập bằng số "count" khai báo vào. Đồng thời, Sau 3 chu kì sau khi gửi input vào DUT ta sẽ tiến hành dump giá trị ra log file để quan sát. tiếp đó ta đợi 3 chu kì tiếp theo để gửi tiếp tín hiệu vào DUT.

Khối monitor:

`ifndef ADDER_MONITOR_SV
`define ADDER_MONITOR_SV
class Monitor;
  adder_base_object adder_object;
  mailbox mon2chk;
  virtual adder_if.TESTBENCH test_port;
  integer ck = 0;

  function new(mailbox mon2chk, virtual adder_if.TESTBENCH test_port);
    this.mon2chk    =  mon2chk;
    this.test_port  =  test_port;
  endfunction

  task run();
    forever begin
      @ (posedge test_port.clk);
        ck++;
      if ( ck == 3 ) begin
        @ (negedge test_port.clk);
        ck = 0;
        adder_object = new();
        $display("@%4d    sum: %0d  carry_out: %x", $time, test_port.testbench.sum, test_port.testbench.carry_out);
        adder_object.sum = test_port.testbench.sum;
        adder_object.carry_out = test_port.testbench.carry_out;
        mon2chk.put(adder_object);
      end
    end
  endtask

endclass

`endif

Đây là một khối thụ động tức là không điều khiển tín hiệu mà chỉ thực hiện chức năng quan sát các output được gửi từ DUT ra ngoài.


Khối Scoreboard:

`ifndef ADDER_SCOREBOARD_SV
`define ADDER_SCOREBOARD_SV
class Scoreboard;
  adder_base_object adder_object_in, adder_object_out;
  mailbox mon2chk;
  mailbox agt2scb;
  integer real_sum;
  bit [2:0] expect_sum;
  bit expect_carry_out;
  bit flag = 0;

  function new(mailbox mon2chk, agt2scb);
    this.mon2chk = mon2chk;
    this.agt2scb = agt2scb;
  endfunction

  task run();
    forever begin
      adder_object_in = new();
      adder_object_out = new();
      mon2chk.get(adder_object_out);
      agt2scb.get(adder_object_in);
      if (flag == 0) begin
        flag = 1;
        if (adder_object_in.rst_n == 0) begin
          expect_sum = 0;
          expect_carry_out = 0;
        end
        else begin
          expect_sum = adder_object_in.in_a + adder_object_in.in_b + adder_object_in.carry_in;
          real_sum = adder_object_in.in_a + adder_object_in.in_b + adder_object_in.carry_in;
          if (real_sum > 7) begin
            expect_carry_out = 1;
          end
          else begin
            expect_carry_out = 0;
          end
        end
      end
      else begin
        if ( (expect_sum != adder_object_out.sum) || (expect_carry_out != adder_object_out.carry_out) ) begin
          $display("An error occurs at @%4d: expect sum: %0d  sum: %0d          expect carry out: %0d  carry out: %0d", $time
                                                                                                                     , expect_sum
                                                                                                                     , adder_object_out.sum
                                                                                                                     , expect_carry_out
                                                                                                                     , adder_object_out.carry_out);
        end
        else begin
          $display("PASSED a data test case");
        end

        if (adder_object_in.rst_n == 0) begin
          expect_sum = 0;
          expect_carry_out = 0;
        end
        else begin
          expect_sum = adder_object_in.in_a + adder_object_in.in_b + adder_object_in.carry_in;
          real_sum = adder_object_in.in_a + adder_object_in.in_b + adder_object_in.carry_in;
          if (real_sum > 7) begin
            expect_carry_out = 1;
          end
          else begin
            expect_carry_out = 0;
          end
        end
      end
    end
  endtask

endclass

`endif


Khối này có vẻ hơi dài nhỉ. Đơn giản là lấy tín hiệu output được gửi từ DUT -> Monitor và đồng thời lấy tín hiệu input (Gửi từ khối driver đến interface) từ inteface để tính toán kết quả mong muốn. Đem kết quả đó so sánh với kết quả nhận được từ khối monitor và in ra màn hình kết quả so sánh.

Khối testbench top:

`include "Adder_Interface.sv";
program test(adder_if.TESTBENCH test_port);
  `include "Base_Object.sv"
  `include "Generator.sv"
  `include "Driver.sv"
  `include "Agent.sv"
  `include "Monitor.sv"
  `include "Scoreboard.sv"
  `include "Testbench_Environment.sv"
  TB_environment env;

  initial begin
    env = new();
    env.gen_cfg();
    env.build();
    env.run();
    env.wrap_up();
  end


endprogram


Khối này là gọi các function thôi. hông có gì nhiều.

Khối env:

`ifndef ADDER_TESTBENCH_ENVIRONMENT_SV
`define ADDER_TESTBENCH_ENVIRONMENT_SV
class TB_environment;

  Generator gen;
  Agent agt;
  Driver drv;
  Monitor mon;
  Scoreboard scb;
  mailbox gen2agt, agt2drv, agt2scb, mon2chk;

  extern function new();
  extern function void gen_cfg();
  extern function void build();
  extern task run();
  extern task wrap_up();

endclass


function TB_environment::new();
  //cfg = new();
endfunction


function void TB_environment::gen_cfg;
//  assert(cfg.randomize);
endfunction


function void TB_environment::build();
// Initialize mailboxes
  gen2agt = new();
  agt2drv = new();
  mon2chk = new();
  agt2scb = new();
  // Initialize transactors
  gen = new(gen2agt);
  agt = new(gen2agt, agt2drv, agt2scb);
  drv = new(agt2drv, test_port);
  mon = new(mon2chk, test_port);
  scb = new(mon2chk, agt2scb);
endfunction


task TB_environment::run();
  fork
    gen.run(200);
    agt.run();
    drv.run(200);
    mon.run();
    scb.run();
  join
endtask


task TB_environment::wrap_up();
  fork
//    gen.wrap_up();
//    agt.wrap_up();
//    drv.wrap_up();
//    mon.wrap_up();
//    chk.wrap_up();
//    scb.wrap_up();
  join
endtask
`endif



Mình đã hướng dẫn cách dùng Ncsim và VCS online miễn phí trong các bài trước. Các bạn tham khảo và chạy kiểm tra kết quả nhé.





Cách tính BW và latency trong 1 hệ thống SoC sử dụng chuẩn giao tiếp AXI protocol

Tác giả:  TrongTran Ngày:  31/12/2019 Nếu bạn nào đang làm về verification cho system performance (ST) thì bài này sẽ bổ ích cho bạn. Ngày ...