Monday, September 16, 2019

[Design][Thiết kế CPU 32 bit] [Bài 2] Tìm hiểu về tập lệnh assembly trong thiết kế CPU 32 bit

Tác giả: TrongTran
Ngày: 16/09/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.


Câu lệnh “ADD”

Cú pháp:                                   add rd, rs1, rs2

Với câu lệnh add bên trên CPU sẽ thực hiện 2 tác vụ đó là:


  • Reg[rd] = Reg[rs1] + Reg[rs2]
  • PC = PC + 4


Ý nghĩa của câu lệnh này là lấy giá trị chứa trong thanh ghi rs1 cộng với giá trị chứa trong thanh ghi rs2 và gán giá trị đó vào thanh ghi rd. Sau đó giá trị của PC = PC + 4.

Ví dụ:
Ta có thanh ghi R5 = 1 và R6 = 2.
Ta thực hiện câu lệnh

                           add r7, r5, r6

Sau khi thực hiện phép toán r7 sẽ chứa giá trị 1 + 2 = 3.

Vậy tại sao lại là PC = PC +4 mà không phải cộng 1 số nào khác. Đơn giản vì câu lệnh của ta sẽ có 32 bit (CPU 32 bit).
Mà trong memory thì mỗi đơn vị PC chứa 8 bit.

Chẳng hạn:
Ta có file IMEM như sau:

0001_0011 //PC
1000_1111 //PC+1
0101_1010 //PC+2
1111_0000 //PC+3
1010_1010 //PC+4

Vì câu lệnh của chúng ta có 32 bit nên ta cần ghép 4 dòng từ PC đến PC+3 để thành 1 câu lệnh 32 bit hoàn chỉnh. Sau khi thực hiện xong câu lệnh tại PC. Có phải câu lệnh tiếp theo chúng ta bắt đầu từ PC+4 không.

Câu lệnh “SUB”

Cú pháp:
                                   sub rd, rs1, rs2

Với câu lệnh sub bên trên CPU sẽ thực hiện 2 tác vụ đó là:

  • Reg[rd] = Reg[rs1] - Reg[rs2]
  • PC = PC + 4

Ý nghĩa của câu lệnh này là lấy giá trị chứa trong thanh ghi rs1 trừ cho giá trị chứa trong thanh ghi rs2 và gán kết quả vào thanh ghi rd. Sau đó tăng giá trị của PC = PC + 4 cho câu lệnh kế tiếp.

Ví dụ:
Ta có thanh ghi R5 = 6 và R6 = 2.
Ta thực hiện câu lệnh

                           sub r7, r5, r6


Sau khi thực hiện phép toán r7 sẽ chứa giá trị 6 - 2 = 4.

Ngoài ra ta còn có 1 số câu lệnh khác tương tự như:


  • and: and giá trị chứa trong hai thanh ghi rs1 và rs2 gán vào rd.
  • or: or giá trị chứa trong hai thanh ghi rs1 và rs2 gán vào rd.
  • xor: xor giá trị chứa trong hai thanh ghi rs1 và rs2 gán vào rd.
  • sll: câu lệnh shift left 
  • slt: Câu lệnh so sánh có dấu
  • sltu: Câu lệnh so sánh không dấu
  • srl: Câu lệnh shift right logical
  • sra: Câu lệnh shift right algorithm


Ở đây sẽ có nhiều bạn dễ nhầm lẫn giữa câu lệnh srl và sra , và giữa slt và sll. Để làm rõ sự khác biệt giữa srl và sra mình lấy ví dụ như sau: 

Mình có 1 số 8 bit A = 8’b11001010
Khi dùng lệnh srl shift right 2 đơn vị kết quả là 8’b00111010
Khi dùng lệnh sra shift right 2 đơn vị kết quả là 8’b11110010
Như vậy bit dấu được mở rộng trong câu lệnh sra. Còn câu lệnh srl thì mặc định mở rộng bit 0.
Nếu bit dấu là 0 thì khi shift right với 2 câu lệnh trên kết quả là như nhau.

Phân biệt 2 câu lệnh sll và slt.
Ý nghĩa của câu lệnh sll chỉ đơn giản là shift left giá trị chứa trong thanh ghi.
Mình lấy ví dụ:

rs1 = 8’b10001010 (Ở đây mình giả sử các thanh ghi chứa 8 bit data chứ không phải 32 bit cho bạn dễ nhìn thấy).
rs2 =8’b00000010 = 2.

                                   sll rd, rs1, rs2

Có nghĩa là shift left giá trị chứa trong thanh ghi rs1 đi 2 lần

Rd = 8’b10001010 << 2 = 8’b00101000

Nó đơn giản vậy thui.

Tương tự ta có câu lệnh slli

                                        sll rd, rs1, A

A là một số integer mình lấy ví dụ:

Rs1 = 8’b10001010 (Ở đây mình giả sử các thanh ghi chứa 8 bit data chứ không phải 32 bit cho bạn dễ nhìn thấy).
A  = 2.
Có nghĩa là shift giá trị chứa trong thanh ghi rs1 đi 2 lần
Rd = 8’b10001010 << 2 = 8’b00101000


Bây giờ chúng ta cùng tìm hiểu câu lệnh slt nhé:

Slt nghĩa là “set if less than” từ “s” đầu không phải là shift đâu nhé. 

                                       slt rd, rs1, rs2

Câu lệnh trên có nghĩa là nếu giá trị chứa trong thanh ghi rs1 nhỏ hơn giá trị chứa trong rs2 thì giá trị thanh ghi rd sẽ được set đến 1, Ngược lại là 0.
Mình lấy ví dụ:
Mình có x5 chứa giá trị 9, x6 chứa giá trị 10.

                                     slt x1, x5, x6

Sau khi thực hiện lệnh này vì 9 < 10, nên x1 sẽ được set = 1 (x1 chứa giá trị 1).
Câu lệnh slti: Câu lệnh này giống với câu lệnh slt nhưng thay rs2 bằng một số nguyên.
Mình lấy ví dụ:
Mình có x5 chứa giá trị 9

                                      slti x1, x5, 6


Vì 9 > 6 nên giá trị x1 sẽ bằng 0.


Câu lệnh “ADDI”

Cú pháp:
                                          addi  rd,rs1,A

Ý nghĩa của câu lệnh này là lấy giá trị chứa trong thanh ghi rs1 cộng với A (A là một số integer) và gán kết quả vào thanh ghi rd. Sau đó tăng giá trị của PC = PC + 4 cho câu lệnh kế tiếp.

Câu lệnh “SUBI”

Cú pháp:
                                     subi  rd,rs1,A 

Ý nghĩa của câu lệnh này là lấy giá trị chứa trong thanh ghi rs1 trừ cho A(A là một số integer)  và chứa giá trị kết quả trong thanh ghi rd. Sau đó tăng giá trị của PC = PC + 4 cho câu lệnh kế tiếp.
Vì trong câu lệnh giá trị A chỉ là 12 bit data. Vì vậy trước khi thực hiện cộng hoặc trừ số nhị phân ta phải mở rộng bit. Mở rộng bit có 2 dạng là mở rộng un-signed và signed.


Câu lệnh lw, lh, lb  (Load word, load halfword, load byte, load halfword unsigned và load byte un-signed)

Ngoài ra chúng ta còn có 1 số câu lệnh khác như slli, slti, sltui, srli, srai…v..v.

Cú pháp:
                                             lw rd, A(rs1)

Ý nghĩa của câu lệnh này là lấy giá trị chứa trong thanh ghi rs1 cộng với số A (Nếu là lệnh lw thì A nên là bội số của 4, nếu là lh thì A nên là bội số của 2 và lb thì A bất kì). Gọi kết quả là B = Reg{x2} + A. Ta tìm trong DMEM lấy giá trị tại địa chỉ B (Giá trị B chính là địa chỉ để ta truy vấn giá trị cần lấy) và gán vào thanh ghi rd.

Sự khác biệt giữa lw và lh và lb.

lw nghĩa là ta lấy 32 bit data (1 word) trong địa chỉ B và gán vào thanh ghi rd.
Trong khi lb là ta lấy 8 bit data (1 byte) trong địa chỉ B và mở rộng bit dấu thành 32 bit và gán vào thanh ghi rd.
Ở đây ta còn có lệnh lh nghĩa là load half word = 2 byte (Địa chỉ nên là bội số của 2).
Ngoài ra, ta còn có 1 số câu lệnh khác đó là:
lbu là “load unsigned byte” có nghĩa là ta mở rộng bit bằng cách thêm 0 vào 24 bit đầu MBS (không dấu) còn với LB thì ta mở rộng bit bằng bit dấu (Bit đầu tiên từ trái qua phải).
lhu là “load unsigned halfword” tương tự như trên.

Câu lệnh sw, sb, sh (Store word, store byte, Store halfword)

Cú pháp:
                                            sw rd, A(rs1)

Ý nghĩa của câu lệnh này là lấy giá trị chứa trong thanh ghi rs1 cộng với số A (Nếu là lệnh sw thì A nên là bội số của 4, sh thì A nên là bội số của 2, nếu là sb thì A bất kì). Gọi kết quả là B = Reg{x2} + A. Ta lấy giá trị chứa trong thanh ghi rd và lưu vào địa chỉ B (Giá trị B chính là địa chỉ để ta truy vấn giá trị cần lấy). Câu lệnh này ngược lại với câu lệnh lw.


Lệnh nhảy có điều kiện beq, bne, blt, bge, bltu, bgeu

Cú pháp:
                                          beq rs1, rs2, A

Câu lệnh này nhằm thay đổi câu lệnh được thực hiện tiếp theo bằng cách thay đổi giá trị của PC. Thông thường sau khi thực hiện 1 câu lệnh giá trị PC = PC + 4. Tuy nhiên, trong câu lệnh trên nếu giá trị trong thanh ghi rs1 bằng giá trị trong thanh ghi rs2 thì PC = PC + A (Không phải + 4 và giá trị của A nên là bội của 4 chẳng hạn 8, 16, 20 …v..v).
Tương tự ta có câu lệnh: bne nghĩa là nhảy khi rs1 # rs2 (Không bằng).

blt: nhảy khi rs1 < rs2 (nhỏ hơn).
bge: Nhảy khi rs1 >= rs2.
bltu: Tương tự như lệnh blt nhưng so sánh các số không dấu.
bgeu: Tương tự như lệnh bge nhưng so sánh các số không dấu.


Câu lệnh jal (Jump and load), jalr (Jump and load register)

Cú pháp:
                                           jal rd, A

Câu lệnh này thực hiện 2 chức năng đó là 
Gán giá trị PC+4 vào thanh ghi rd (Load)
Gán giá trị PC = PC + A (Jump).
Đây là câu lệnh nhảy không điều kiện.
Như vậy câu lệnh tiếp theo ta thực hiện sẽ là PC = PC + A. Tuy nhiên, ta vẫn có thể biết được vị trí mà PC sẽ chạy tiếp theo nếu không jump nhờ vào giá trị PC + 4 được gán vào thanh ghi rd.

                                  jalr rd, rs1, A

Câu lệnh này thực hiện 2 chức năng đó là 
Gán giá trị PC+4 vào thanh ghi rd.
Gán giá trị PC = Reg{rs} + A.
Câu lệnh này khác câu lệnh jal ở trên chỗ giá trị của PC được gán bằng Reg{rs} + A thay vì bằng PC + A.

Câu lệnh "LUI"

Cú pháp:
                                      lui rd, K

K: Là 1 số.

Ý nghĩa của câu lệnh này là nhằm gán một số rất lớn vào một thanh ghi.
Thông thường để gán 1 số vào 1 thanh ghi ta sẽ dùng lệnh add.
Chẳng hạn:

                                         addi r4, r4, 0
                                         addi r5, r4, A

Trong đó A (tối đa 12 bit và được mở rộng bit dấu) là số muốn add vào thanh ghi r5.
Tuy nhiên trong câu lệnh add, giá trị của A bị giới hạn bởi 12 bit data. Nghĩa là tối đa chúng ta gán vào thanh ghi r5 là 000007FE (Các số 0 đầu là do mở rộng bit).
Với câu lệnh lui ta có thể gán số lớn nhất là FFFFF000 vào thanh ghi r5.
Cú pháp:

                                            lui x10, K

K là số có tối đa 20 bit data. Với câu lệnh lui, K được mở rộng về bên phải 12 bit data nữa là 32 bit.
Mình lấy ví dụ:

                                         lui x10, 0xFE06F

Nghĩa là mình sẽ gán được 1 số có giá trị 0xFE06F000 vào thanh ghi x10, Trong đó 12 bit cuối (0x000) bên phải được tự động mở rộng.

Ngoài ra ta còn có câu lệnh li (gán có dấu)

                                  li x10, 0xFF04FEEF

Câu lệnh AUIPC

Cú pháp:
                                    auipc rd, A

Câu lệnh này có nghĩa là gán giá trị PC=PC+A (A được mở rộng 12 bit bên phải giống câu lệnh lui bên trên) và lưu giá trị đó vào thanh ghi rd.

Câu lệnh này dùng giống như các câu lệnh nhảy tuy nhiên ta dùng với một đoạn code dài mà các câu lệnh nhảy khác như jal, beq,..v… không đáp ứng được.


[Design][Thiết kế CPU 32 bit] [Bài 1] Các khái niệm cơ bản trong thiết kế CPU RISC-V 32 bit

Tác giả: TrongTran
Ngày: 16/09/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à chuỗi bài hướng dẫn thiết kế 1 CPU RISC-V 32 bit.

Mình nói 1 chút về xu hướng phát triển của RISC-V nhé. Xu hướng trước đây đó là CPU càng thực hiện được nhiều câu lệnh thì càng tốt. Nghĩa là các nhà thiết kế luôn hướng đến việc thiết kế sao cho CPU có thể thực hiện nhiều câu lệnh nhất có thể.

Ví dụ: Kiến trúc VAX đã có thể hỗ trợ nhân đa thức.

Tuy nhiên, Về sau người ta nhận thấy rằng để thiết kế một CPU có thể chạy nhiều câu lệnh làm cho area tăng đáng kể và năng lượng tiêu tốn cũng cao, thời gian xử lí dài. Từ đó một xu hướng mới xuất hiện đó là CPU thực hiện càng ít câu lệnh càng tốt nhưng phải đảm bảo đầy đủ chức năng của nó.
Trong bài này, chúng ta sẽ tìm hiểu một số khái niệm cơ bản trước khi đi vào thiết kế CPU RISC-V 32 bit chi tiết.

1. Mã máy

Máy tính không thể nào hiểu được các câu lệnh bậc cao mà bạn vẫn hay viết hằng ngày. Những câu lệnh đó mang các từ gợi nhớ và đa dạng (Tuỳ loại ngôn ngữ mà có các tập lệnh khác nhau). Nó giúp cho lập trình viên dễ dàng ghi nhớ, tránh sai sót và nhầm lẫn trong quá trình code.  Để chạy được các câu lệnh đó ta phải sử dụng 1 trình biên dịch để chuyển từ đoạn code viết bằng ngôn ngữ bậc cao sang mã máy. 
Mã máy là đoạn mã chỉ bao gồm các bit 0 hoặc 1 mang các ý nghĩa nhất định tuỳ vào cách định nghĩa của CPU.
Ví dụ :
CPU 16 bit sẽ có mã của câu lệnh “add” khác với CPU 32 bit.
Ưu điểm của ngôn ngữ bậc cao so với mã máy.
Hãy tưởng tượng bạn chỉ code với mã máy và làm việc với các chỉ số 0/1 thì công việc sẽ trở nên khó khăn hơn, khó quan sát, dễ nhầm lẫn và tốn nhiều thời gian để học.
Ngôn ngữ bậc cao giúp bạn dễ nhớ, học và làm việc nhanh hơn.

Dưới đây là mô hình cấu trúc của ngôn ngữ

Ngôn ngữ bậc cao

temp = v[k];
v[k] = v[k+1];
v[k+1] = temp;

Ngôn ngữ assembly

lw x3, 0(x10);
lw x4, 4(x10);
sw x4, 0(x10);
sw x3, 4(x10)

Mã máy

1000 1101 1110 0010 0000 0000 0000 0000
1000 1110 0001 0000 0000 0000 0000 0100
1010 1110 0001 0010 0000 0000 0000 0000
1010 1101 1110 0010 0000 0000 0000 0100

2. Thanh ghi.

Ta có thể nhìn nhận như sau:
CPU 16 bit: Sẽ có 16 thanh ghi (Từ R0 -> R15).
CPU 32 bit: Sẽ có 32 thanh ghi để xử lí (từ R0 -> R31).
CPU 64 bit: Sẽ có 64 thanh ghi (Từ R0 -> R63).
Trong đó sẽ có một vài thanh ghi mang các giá trị đặc biệt không đổi. Nghĩa là dù ta có WRITE bất kì giá trị nào vào đi nữa thì giá trị thanh ghi đó vẫn không thay đổi giá trị.
Chẳng hạn, trong bài này, Mình thiết kế CPU 32 bit có thanh ghi R0 luôn mang giá trị 0.

Ngoài ra, sẽ có các vùng thanh ghi thực hiện các nhiệm vụ đặc biệt chẳng hạn như vùng chứa các biến tạm trong tính toán, vùng chứa flag ..v…v.

3. Đơn vị

Như các bạn đã biết có rất nhiều cách định nghĩa 1 word:
1 word = 16 bit.
1 word = 32 bit.
1 word = 64 bit.
….
Trong bài này do chúng ta thiết kế CPU 32 bit nên mình sẽ quy định luôn
1 word = 4 byte = 32 bit.
1 half word = 2 byte = 16 bit.

4. Cách tính địa chỉ read/write.

Trong memory mỗi một đơn vị địa chỉ sẽ chứa tối đa 8 bit data = 1 byte data.

Nghĩa là nếu bạn muốn đọc dữ liệu 1 byte data từ memory và đọc 2 vùng data trên memory liên tục nhau thì bạn cộng địa chỉ lên 1 đơn vị.
Địa chỉ 01: Data AA (AA: mã hex 8 bit data).
Địa chỉ 02: Data BB (BB: mã hex 8 bit data).

Trong trường hợp ta không READ/WRITE 1 lần 8 bit data mà READ/WRITE 1 lần 32 bit thì nên cộng địa chỉ với 4.

5. Big endian và little endian

Big Endian
Least Significant bit (Bit có trọng số thấp nhất trong chuỗi dữ liệu) luôn được lưu ở ô nhớ có địa chỉ lớn nhất còn Most Significant Bit (Bit có trọng số cao nhất trong chuỗi dữ liệu) được lưu ở ô nhớ có địa chỉ nhỏ nhất trong vùng lưu trữ của biến.

Little Endian
Ngược lại với các nền tảng sử dụng big endian, ở các nền tảng sử dụng little endian, LSB luôn được lưu ở ô nhớ có địa chỉ nhỏ nhất còn MSB được lưu ở ô nhớ có địa chỉ lớn nhất trong vùng lưu trữ của biến.


Bài tiếp theo chúng ta sẽ tìm hiểu về tập lệnh assembly trong thiết kế CPU RISC-V 32 bit.

Thursday, September 12, 2019

[Verification] Tìm hiểu về reset trong một hệ thống SoC và một số trường hợp đặc biệt của reset

Nguồn tham khảo: Bài viết “RESET VERIFICATION IN SOC

DESIGNS
” của các tác giả “CHRIS KWOK, PRIYA VISWANATHAN AND KURT TAKARA” và tổng hợp kiến thức bản thân.

Đây là bài báo khoa học mới nhất từ mentor graphic và mình đọc thấy rât là hay hi vọng bạn cũng học được từ nó. 

Mình sẽ còn update phần hướng dẫn verify cho reset.

Tổng hợp, biên dịch, bổ sung và soạn thảo: TrongTran.


I. GIỚI THIỆU


Ngày nay, các thiết kế trong 1 hệ thống SoC có thể tích hợp từ nhiều khối IP được thiết kế từ nhiều nhà cung cấp khác nhau, mỗi nhà cung cấp lại có cách thiết lập reset cho IP riêng. 

Kiến trúc reset của hệ thống cũng có thể trở nên rất phức tạp. 

Một hệ thống SoC có thể có nhiều nguồn reset, chẳng hạn như:

  • Power-on reset
  • Hardware reset
  • Debug reset
  • Software reset
  • Reset theo dõi bộ đếm thời gian

Các lỗi trong quá trình design liên quan đến reset có thể dẫn đến hiện tượng mestabilities, trục trặc hoặc các lỗi chức năng khác của hệ thống. Hơn nữa, các tương tác phức tạp có thể xảy ra với sự hội tụ của nhiều reset, nhiều clock và nhiều domain.

Trong nhiều trường hợp, điều này dẫn đến cây reset (reset tree) lớn hơn và phức tạp hơn cây clock (clock tree). Nhiều mối lo ngại liên quan đến việc tổng hợp và cân bằng tải của clock tree hiện cũng áp dụng cho reset tree. Rõ ràng, đó là một thách thức để đảm bảo rằng tất cả các nguồn reset truyền một cách an toàn đến các điểm đến mong muốn trong mọi điều kiện.


Trước đây, mô phỏng là phương pháp chính được sử dụng để verify cho hệ thống reset, thường phụ thuộc nhiều vào mô phỏng mức cổng. Tuy nhiên, các thử nghiệm mô phỏng cấp RTL thường không đầy đủ và mô phỏng cấp độ cổng chỉ có thể được chạy tại các phase khá muộn trong flow thiết kế. Thậm chí tệ hơn nữa là các lỗi liên quan đến reset thường có tính chất rất nghiêm trọng, khiến chip hoàn toàn không sử dụng được. 

Nghiêm trọng hơn, thiết kế có thể tiêu thụ quá nhiều năng lượng trong quá trình xác nhận reset, khiến thiết bị quá nóng và bị hỏng vĩnh viễn. Tất cả các yếu tố này có thể khiến chúng ta phải thay đổi design tại các phase cuối gây tốn kém; và trong trường hợp xấu nhất, nó có thể gây thiệt hại nhiều triệu đô la và sự chậm trễ để tung sản phẩm ra thị trường.



II. NHỮNG VẤN ĐỀ CHUNG


Trước tiên, mình nhấn mạnh một số vấn đề về reset phổ biến dựa vào kinh nghiệm thực tế của bản thân.

Mình sẽ tách nó thành hai loại chính:

  • Các vấn đề liên quan đến tính chính xác của cây reset (Reset tree).
  • Các vấn đề liên quan đến việc sử dụng reset.


a. Cây reset (Reset tree).


Đầu tiên mình sẽ đề cập đến những vấn đề liên quan đến việc triển khai reset không chính xác. Những vấn đề này thường được phát hiện trong cây reset (Reset tree). 

Hình bên dưới cho thấy một ví dụ, nếu cây reset này không chính xác, thì tất cả các kiểm tra khác có nguồn từ nó trong thiết kế cũng sẽ không chính xác và chip sẽ không hoạt động đúng.

Trong hình bên dưới nếu tín hiệu rst hoạt động không đúng thì 2 tín hiệu outputs là rsta và rstb cũng hoạt động không đúng từ đó các mạch về sau cũng không thể verify chính xác được.


Nhìn chung, reset chỉ có thể được định nghĩa là:

Reset không đồng bộ với clock 
Reset đồng bộ với clock

Đôi khi, Một tín hiệu reset có thể vừa đồng bộ với clock1 và vừa bất đồng bộ với clock2. Điều này thường gây ra sự hiểu lầm về reset trong hệ thống. Hình bên dưới cho thấy một sơ đồ đơn giản trong đó reset được sử dụng cả không đồng bộ và đồng bộ. 



Trong hình trên khối A là khối reset đồng bộ với tín hiệu clock. Còn khối B là khối reset bất đồng bộ với clock.

Ta xét đơn giản:

Tại khối B, Khi RST = 0, Ngay lập tức tín hiệu ngõ ra của Flip Flop trở về trạng thái ban đầu bất chấp clock tại thời điểm đó là lên hay xuống.
Tại khối A, Khi RST = 0, lúc đó bộ mux2to1 sẽ cho tín hiệu 1’b0 đi vào chân D của Flip Flop và khi có clock cạnh lên thì Flip flop mới gửi tín hiệu từ chân D = 0 qua chân Q ra ngoài. Như vậy đó là reset đồng bộ.

Đôi khi trong quá trình thiết kế, Logic sai có thể được chèn vào cây reset. Logic không chính xác có thể đến trong các hình thức khác nhau. Một số vấn đề phổ biến bao gồm việc bổ sung bộ đệm và cổng tristate như cổng XOR, Hai hình bên dưới:


Việc triển khai phân phối reset bao gồm reset nguồn, reset hierarchy và reset của các phần tử logic, tất cả đều được liệt kê chi tiết trong cây reset được tạo bởi các công cụ. Rà soát các chi tiết của thông tin cây reset có thể giải quyết nhiều vấn đề về thiết kế reset như mình phân tích bên trên.

b. Sử dụng reset.

Nhóm thứ hai của các vấn đề
về reset có liên quan đến việc sử dụng tín hiệu reset để khởi động thiết kế.

Để mô tả tập hợp các vấn đề này, trước tiên chúng ta cần xác định các thuật ngữ có tên là reset
domain và clock domain. Reset domain được đặc trưng bởi các thuộc tính sau:

a) Loại: Đồng bộ hoặc không đồng bộ
b) Phân cực: Hoạt động thấp hoặc hoạt động cao
c) Giá trị: Được set với 1 và reset với 0
d) Là tín hiệu reset trên cùng (Top reset).

Nhiều tín hiệu reset domain đồng bộ có thể được nhóm lại với nhau. Tương tự, Nhiều nguồn clock đồng bộ có thể được nhóm lại trong một clock domain.

Khi reset chuyển từ trạng thái đang reset sang trạng thái hoạt động (từ 0 -> 1). Tất cả tín hiệu reset không đồng bộ phải được đồng bộ hóa với miền clock trước khi được sử dụng. Nếu vi phạm có thể gây ra hiện tượng mestabilities.


Để bảo vệ mạch khỏi hiện tượng mestabilities do reset, cần phải chèn một mạch đồng bộ reset 2 Flip Flop. Hai hình bên dưới hiển thị mạch đồng bộ tín hiệu reset 2 FF điển hình. Mình sẽ giải thích ngắn gọn vấn đề này như sau khi một thiết kế đang hoạt động bình thường. Chúng ta sẽ có các hành động như sau:

Bước 1: Reset hệ thống nghĩa là RST =  0 vài chu kì.
Bước 2: Bật lại hệ thống nghĩa là RST = 1.

Tại bước 1 hệ thống có thể reset bất đồng bộ (RST từ 1 -> 0). Điều này không ảnh hưởng gì quá trình hoạt động.





Tuy nhiên, tại bước 2, khi muốn chuyển từ trạng thái đang reset sang trạng thái hoạt động, tín hiệu reset cần phải bậc đồng bộ với clock để tránh hiện tượng mestabilities như trình bày bên trên.

 Hình sau cho thấy dạng sóng ở đầu ra khi chèn mạch đồng bộ. Lưu ý rằng hiện tượng mestabilities không còn xảy ra và việc khởi động hệ thống (Không reset nữa) bị hoãn bởi một chu kỳ tại ngõ ra Q của Flip Flop thứ 2.



Vậy khi sau khi reset đi qua mạch đồng bộ tín hiệu reset 2 Flip Flop ta tiếp tục phân tích tiếp 2 vấn đề phổ biến.Khi reset được đồng bộ hóa, các nhà thiết kế phải đảm bảo rằng các tín hiệu reset được sử dụng đúng cách trong các mạch phía sau. Hai vấn đề phổ biến cần được quan tâm đó là:

  • Reset được sử dụng với phân cực đúng cho các mạch phía sau 
  • Reset được sử dụng với clock đúng cho các mạch phía sau.


Hình bên dưới đã nêu ra 1 trường hợp sai trong việc sử dụng reset bất đồng bộ với clock cho các mạch phía sau. Lỗi này có thể được phát hiện bởi việc phân tích Clock-Domain Crossing (CDC).




Tiếp theo ta có 1 vấn đề khác đó là FF phía sau đang sử dụng reset tích cực mức cao, trong khi mạch đồng bộ tín hiệu reset 2 Flip Flop phía trước là dành cho reset tích cực mức thấp. Vì đây không phải là giao thoa CDC, lỗi này sẽ không bị phát hiện bằng cách phân tích CDC và không thể bị phát hiện dễ dàng.





c. Reset nhiều miền (RDC).

Nhóm thứ ba của các vấn đề reset có liên quan đến các đường dẫn đặt lại tên miền (RDC) reset. Trường hợp đơn giản nhất của RDC là khi flip flop truyền và nhận được đặt tại các tín hiệu thuộc các miền reset khác nhau.
Hình dưới cho thấy một đường dẫn RDC đơn giản. Nếu RST1 bị reset trong khi RST2 không được reset, DFF2 có thể lấy mẫu dữ liệu bất đồng bộ. Điều kiện để DFF2 lấy mẫu dữ liệu bất đồng bộ là nếu lúc tín hiệu reset của DFF1 bị reset tại thời điểm trong setup time và hold time thì đầu ra DFF2 sẽ bị hiện tựng mestabilities, như trong dưới.






III. PHẦN KẾT LUẬN

Trong bài viết này, mình đã trình bày một số vấn đề
phổ biến về reset, các khó khăn khi verify và đề xuất một giải pháp toàn diện để giải quyết những thách thức này. Mình cũng đã chứng minh tính hiệu quả của giải pháp thông qua việc xác minh một số thiết kế của khách hàng và trình bày các vấn đề đã phát hiện được. Mình cũng chỉ ra nhiều vấn đề trong số này rất khó để phát hiện bằng các kỹ thuật mô phỏng hoặc xác minh truyền thống. 

Cảm ơn mọi người đã đọc.










[Design] Thảo luận 1 vấn đề trong thiết kế Verilog có thể gây sai function trong gate netlist


Tác giả: TrongTran
Ngày: 12/09/2019.
Cập nhật: 14/10/2019.


Trong đợt cập nhật trước thì mình đề cập đây là một lỗi syntax. Tuy nhiên, Có một anh đã góp ý cho mình rằng đây không phải là một lỗi syntax. Việc design như thế này là không gây ra lỗi. Tuy nhiên, chúng ta không khuyến khích design theo cách này. Vì logic out ra ngoài có thể không đúng với function. Mình đổi lại từ "lỗi" thành từ "vấn đề" để tránh nhầm lẫn (thật ra mình cũng không biết nên gọi bằng từ gì cho đúng).

Đây là một phương thức design mà một anh trong nhóm mình vô tình phát hiện khi tổng hợp gate từ verilog code.

Mình đã tiến hành kiểm nghiệm và thật ngạc nhiên là hầu hết các tool verify hiện nay đều không thể phát hiện được. Thậm chí là cả VCS (Synopsys), NCSIM (Cadence) và DC compiler (Tool tổng hợp gate từ RTL). Nếu DC compiler không thể phát hiện đã đành đằng này ngay cả tool check equivalent giữa gate và RTL cũng không thể phát hiện được (Formality).

Sau khi tiến hành kiểm tra thì mình phát hiện ra chỉ có 1 tool Spyglass của synopsys là có thể phát hiện được cách thiết kế này. Tuy nhiên, chúng ta thường bỏ qua những lỗi được report bởi tool này. Lí do là vì một design lớn thường xuất hiện cả trăm ngàn lỗi (Report bởi spyglass) và người confirm hầu như không thể phát hiện được lỗi nào là thật, lỗi nào có thể bỏ qua.


Đầu tiên mình nói đây là vấn đề liên quan đến việc chặp 2 output với nhau. Bạn sẽ nghĩ ngay đến một module như thế này:

module test (in_0, in_1, in_2, in_3, out);

   input in_0;
   input in_1;
   input in_2;
   input in_3;

   output out;

   wire out;

   assign out = in_0&in_1;
   assign out = in_2&in_3;

endmodule

Trường hợp trên ta có biến out được assign 2 lần.
Đây là một trường hợp phổ biến và chắc chắn là mọi tool đều có thể phát hiện được.

Tuy nhiên, có một trường hợp khác, Ta xét ví dụ như bên dưới:

module test_spyglass (clk, rst_n, in_0, in_1, in_2, in_3, out);

   input clk;
   input rst_n;

   input   in_0;
   input   in_1;
   input   in_2;
   input   in_3;
   output  out;

   reg     tmp_0;
   reg     tmp_1;
   wire    out;

   always @(posedge clk or negedge rst_n) begin
      if (!rst_n) begin
         tmp_0 = 1'b0;
      end else begin
         tmp_0 <= in_0;
      end
   end

   always @(posedge clk or negedge rst_n) begin
      if (!rst_n) begin
         tmp_1 = 1'b0;
      end else begin
         tmp_1 <= in_1;
      end
   end

   module_and_logic module_and_logic_00 (.in_0(in_2), .in_1(tmp_0), .out(out));
   module_or_logic module_or_logic_00 (.in_0(in_3), .in_1(tmp_1), .out(out));



endmodule

module module_and_logic (in_0, in_1, out);

   input   in_0;
   input   in_1;
   output  out;

   assign out = in_0&in_1;

endmodule

module module_or_logic (in_0, in_1, out);

   input  in_0;
   input  in_1;
   output out;

   assign out = in_0|in_1;

endmodule





Cấu trúc của code này như sau:






Hai submodule được gọi và 2 submodule này đều out ra cùng 1 tín hiệu đó là “out”.
Chỉ khác nhau 1 điểm đó là với module bên trên tín hiệu output là từ 2 submodule, còn như ví dụ ban đầu output từ một combinational logic trong module top.

Khi tổng hợp code verilog này thành gate nestlist, Ta sẽ có được 1 gate như sau:

module test_spyglass (clk, rst_n, in_0, in_1, in_2, in_3, out);

   input clk;
   input rst_n;

   input   in_0;
   input   in_1;
   input   in_2;
   input   in_3;
   output  out;

   reg     tmp_0;
   reg     tmp_1;
   wire    out;
   wire HINET_0, HINET_0;


DFF D0 ( .D(in_0), .CP(clk), .CDN(rst_n), .Q(tmp_0) );
DFF D1 ( .D(in_1), .CP(clk), .CDN(rst_n), .Q(tmp_1) );

   
 module_and_logic module_and_logic_00 (.in_0(in_2), .in_1(tmp_0), .out(HINET_1));


   module_or_logic module_or_logic_00 (.in_0(in_3), .in_1(tmp_1), .out(HINET_0));

AND (.A1(HINET_0), .A2(HINET_1), .Z(out) );

endmodule


module module_and_logic (in_0, in_1, out);

   input   in_0;
   input   in_1;
   output  out;

   AND (.A1(in_0), .A2(in_1), .Z(out) );

endmodule


module module_or_logic (in_0, in_1, out);

   input  in_0;
   input  in_1;
   output out;

   OR (.A1(in_0), .A2(in_1), .Z(out) );


endmodule

Như vậy là ta có thêm 1 cổng logic AND (Phần màu đỏ) để “and” 2 tín hiệu đó lại. Đây là lí do mà việc chạy để kiểm tra equivalence giữa RTL và gate lại không phát hiện được. Đây cũng là lí do mình nói function của bạn có thể bị sai. Tại sao ư ? Nếu bạn chỉ verify cho RTL và bạn dùng tool Formality để check equivalence giữa gate và RTL. Bạn tin tưởng rằng RTL của bạn đúng và FM pass. Không có bất kì hành động nào nhằm verify function cho gate. Tuy nhiên, Vấn đề sẽ trở nên nghiêm trọng hơn với việc sai fucntion trên gate.
Mình ví dụ, Đây là 2 tín hiệu out error cho fusa từ 1 IP đến module interrupt. 
Khi có error xảy ra nó sẽ toggle lên 1. Trong trường hợp chèn cổng AND. Tín hiệu chỉ toggle khi cả hai logic đều bằng 1. Điều này không đúng với mục đích function của chúng ta đó là interupt khi 1 trong 2 tín hiệu error toggle.

Tuy nhiên, Tool Spyglass của synopsys hoàn toàn có thể phát hiện ra lỗi này và báo như sau “Non-tristate signal 'test_spyglass.out' has multiple drivers”.

Như vậy, bạn cố gắn lưu ý cách design này để tránh nhé. Đặc biệt là với các bạn thích dùng tool VCS để mô phỏng.


Mọi người để lại comment để góp ý với mình hen. Cảm ơn ạ.

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 ...