• 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

    Vi mạch và Ứng dụng

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

    Vi mạch và Ứng dụng

Thứ Bảy, 23 tháng 9, 2017

[Basic Knowledge][Bài 4] Hướng dẫn phân tích thiết kế lõi IP step-by-step - Mô tả và kiểm tra RTL code

Bạn đang muốn bắt tay vào thiết kế một lõi IP (IP core) nhưng không biết bắt đầu từ đâu? Bài viết này là một phần trong chuỗi bài viết hướng dẫn bạn đi từng bước để có thể thiết kế được một lõi IP hoàn chỉnh. Phương pháp được sử dụng ở đây là top-down, phân tích từ tổng quan đến chi tiết. Ví dụ được trình bày là một thiết kế CPU cơ bản (simple CPU).


Trong bài 1bài 2 và bài 3 chúng ta đã hiểu đến bước phân tích chi tiết từ khối trong một thiết kế vi mạch. Bài này chúng ta sẽ thực hiện mô tả RTL code cho toàn bộ một lõi IP trên ví dụ CPU 8 bit SCPU đã phân tích trong những bài trước.
1. Hướng dẫn mô tả RTL code sử dụng Verilog HDL
1.1 Xác định cấu trúc thứ bậc (hierachy)
Để mô tả RTL code cho một lõi IP hay một thiết kế nói chung, một số bước chính cần được thực hiện như sau:
  1. Xác định cấu trúc thứ bậc của các file
  2. Mô tả RTL code cho từng file, những file có cấp thấp nhất trong cấu trúc sẽ được mô tả trước, các file có cấp cao hơn sẽ được mô tả sau. Thứ tự mô tả RTL code là bottom-to-top. Vì các cấp cao hơn thường sẽ gọi (instance) các file cấp thấp hơn và file cấp cao nhất (top) thường chỉ mô tả các kết nối của các file cấp thấp hơn.
Hình 1. Xác định cấu trúc thứ bậc của các file RTL code sẽ mô tả
Trong ví dụ về CPU 8 bit, chúng ta có 3 khối FETCH, DECODER và EXECUTE. Tương ứng, chúng ta sẽ có 3 file RTL code để mô tả 3 khối này là scpu_fetch.v, scpu_decoder.v và scpu_execute.v. Bên cạch đó là một file để kết nối 3 khối trên là scpu_top.v. Ngoài ra, nếu trong thiết kế có các tham số và định nghĩa được sử dụng trong nhiều file khác nhau thì có thể có thêm các file header để chứa chúng, ví dụ như scpu_define.h, scpu_parameter.h.
Căn cứ theo cấu trúc thứ bậc trên thì các file scpu_fetch.v, scpu_decoder.v và scpu_execute.v sẽ được mô tả trước, file scpu_top sẽ được mô tả sau. Các file scpu_define.h, scpu_parameter.h có thể được mô tả song song với các file trước đó hoặc mô tả sau cùng để tập hợp các tham số và định nghĩa dùng chung.
Ở đây có một số quy định chung để tiện theo dõi như sau:
  1. Mỗi khối sẽ được mô tả bằng một cặp từ khóa module/endmodule nên một khối tương ứng với một module
  2. Mỗi module sẽ được mô tả trong một file riêng tuy nhiên các bạn hoàn toàn có thể mô tả chúng trong cùng một file ".v"
  3. Tên file và tên module sẽ trùng nhau để dễ quản lý. Ví dụ, file scpu_fetch.v sẽ chứa module có tên scpu_fetch.
  4. Tên file và tên module ở đây được đặt theo nguyên tắc <tên module top>_<tên module con>. Ví dụ, scpu_fetch thì scpu là ký hiệu chung chỉ module top, fetch là tên đại diện cho khối chức năng (module con)
  5. File header sẽ có đuôi ".h" chứa các tham số hoặc định nghĩa dùng chung 
1.2 Mô tả RTL code cho từng file
Như đã trình bày, một module ở đây được hiểu là một khối. Một module được mô tả trong một cặp từ khóa module/endmodule. Trước khi đi vào mô tả chi tiết từng file RTL code, chúng ta sẽ xem một file RTL code thường có những thành phần nào.
Một module thường chứa các thành phần cơ bản như sau:
1. Ghi chú ban đầu (file header)
2. Khai báo tiền tổng hợp (pre-synthesis define)
3. Khai báo module
4. Khai báo hằng số (parameter)
5. Khai báo tín hiệu giao tiếp (in/out interface)
6. Khai báo biến và tín hiệu nội (internal signal)
7. Mô tả thân module (module body) 
8. Khai báo endmodule

Trong các thành phần trên thì 3, 5, 7 và 8 là các thành phần bắt buộc phải có. Các thành phần khác có thể có hoặc không tùy vào trường hợp cụ thể. Để minh họa, sau đây, file scpu_fetch.v sẽ được mô tả.

Ghi chú ban đầu là các comment mô tả các thông tin liên quan đến file RTL code như tên công ty, tên dự án, chức năng của file RTL code, tên tác giả, ngày tạo file, các điểm chỉnh sửa trong file so với các phiên bản trước, thông tin quy định về việc phân phối và sử dụng file RTL code và các thông tin liên quan khác. Nội dung phần này sẽ khác nhau tùy quy định từng công ty. Nội dung phần ghi chú ban đầu được đặt sau dấu “//” hoặc trong cặp dấu “/*” và “*/ .
Hình 2. Ví dụ về ghi chú đầu file RTL code của khối FETCH
Khai báo tiền tổng hợp là các chỉ dẫn như `define, `timescale hoặc `include các tập tin chứa các khai báo `define`timescale được sử dụng trong tập tin RTL code. Ví dụ trong thiết kế SCPU, định nghĩa sau được sử dụng để tạo độ trễ khi mô tả mạch tuần tự:
`define DLY #1
Định nghĩa trên dùng để tạo độ trễ 1 đơn vị thời gian trước khi một tín hiệu cập nhật giá trị theo cạnh lên xung clock khi chạy mô phỏng RTL code.
Hình 3. Tạo độ trễ sau cạnh lên xung clock cho các tín hiệu hoạt động theo xung clock
Trong một số trường hợp mô phỏng RTL code, trình mô phỏng cho kết quả không chính xác khi ngõ vào và cạnh lênh xung clock cùng thay đổi tại một thời điểm. Vì vậy, mỗi tín hiệu hoạt động theo xung clock được delay để khi giá trị của nó truyền đến các mạch khác, cũng sử dụng xung clock, thì giá trị này không thay đổi cùng lúc với cạch lên xung clock.
Hình 4. Trình mô phỏng cho kết quả sai ở d_out khi d_in đổi giá trị cùng lúc với cạnh lên clk
Khai báo module là sẽ đi kèm với khai báo endmodule để định nghĩa một khối. ĐI cùng với khai báo module là tên module và danh sách các tín hiệu giao tiếp (input/output/inout) của một module. 
Khai báo hằng số sử dụng từ khóa parameter, localparam hoặc gọi các tập tin chứa khai báo hằng số sử dụng trong RTL code. Khai báo hằng số nằm bên trong khai báo module/endmodule.
Hình 4. Khai báo tiền tổng hợp, khai báo module và khai báo hằng số cho khối FETCH
Khai báo tín hiệu giao tiếp là khai báo các tín hiệu sẽ kết nối đến các module khác bằng các từ khóa input, output hoặc inout. Các tín hiệu giao tiếp này có được từ sơ đồ tín hiệu giao tiếp khi phân tích lõi IP. Một tín hiệu giao tiếp cần có các thông tin quan trọng sau:
  • Chiều tín hiệu (input/output/inout)
  • Loại tín hiệu (reg, wire, ...)
  • Độ rộng tín hiệu

Hình 5. Sơ đồ tín hiệu giao tiếp của các khối

Hình 6. Khai báo tín hiệu giao tiếp trong module scpu_fetch của khối FETCH
Khai báo tín hiệu giao tiếp nội là khai báo các biến nội chỉ được sử dụng trong module và không kết nối đến bất kỳ module nào khác. Phần thân module có thể được mô tả trước khi khai báo phần này vì khi mô tả các mạch nguyên lý cho phần thân module, các biến nội có thể sẽ phát sinh thêm để giúp việc mô tả RTL code dễ dàng hơn. Lưu ý, việc mô tả phần thân module trước không có nghĩ là đoạn code của thân module nằm trên đoạn code khai báo tín hiệu giao tiếp nội trong một module mà vẫn theo nguyên tắc chung là "một biến phải khai báo trước khi sử dụng". Tín hiệu giao tiếp nội cần 2 thông tin quan trọng là loại tín hiệu và độ rộng tín hiệu.
Hình 7. Khai báo tín hiệu giao tiếp nội của khối FETCH
Mô tả thân module là căn cứ trên các mạch nguyên lý và các phân tích chi tiết cấu trúc từng khối để viết code.
Hình 8. Mô tả RTL code cho thanh ghi IR, DR và một phần bộ nhớ memory
Hình 9. Sự tương ứng giữa RTL code và mạch nguyên lý của thanh ghi IR
Hình 10. Sự tương ứng giữa RTL code và mạch nguyên lý của MEMORY
Khai báo endmodule là từ khóa kết thúc việc mô tả RTL code cho một khối.
Hình 11. Khai báo endmodule của khối FETCH
Đối với module có gọi một module khác thì "việc gọi" được viết trong phần thân module và cách gọi đầy đủ như sau:
Hình 12. Gọi và kết nối một module trong một module khác
2. Kiểm tra cú pháp và luật thiết kế (design rules) RTL code
Bước cuối cùng trong công đoạn phân tích thiết kế là kiểm tra các tập tin RTL code trên các phần mềm chuyên dụng. Hai điểm chính mà RTL code phải được kiểm tra là cú pháp và luật thiết kế. Trong đó:
  • Cú pháp là những quy định của ngôn ngữ mô tả phần cứng mà RTL code phải tuân thủ.
  • Luật thiết kế là những quy định khác nhằm hạn chế các nguyên nhân gây ra hoạt động không mong muốn hoặc giúp bạn cải thiện code style (cách viết RTL code) tốt hơn.  

Hình 13. Kiểm tra RTL code với phần mềm LEDA của Synopsys
Thường sẽ có 2 cấp độ chính là:

  • Những cảnh báo phải sửa: nếu không sửa sẽ không thể tổng hợp hay mô phỏng được
  • Những cảnh báo không cần sửa: những lỗi này là không bắt buộc phải sửa nhưng người thiết kế phải kiểm tra từng cảnh báo của phần mềm để đảm bảo những "cảnh báo" đó không phải là một "lỗi"
3. RTL code (Phiên bản chưa mô phỏng cơ bản)
Link download RTL code của SCPU: CPU 8 bit SCPU
pass (nếu có): nguyenquanicd
Các file:
  • scpu_top.v
  • scpu_fetch.v
  • scpu_decoder.v
  • scpu_execute.v
  • scpu_define.h
  • scpu_parameter.h

Hình 14. Kết quả tổng hợp trên FPGA bằng Quartus
Đến đây, chúng ta đã có phiên bản RTL code ban đầu của một lõi IP. Bước cuối cùng là mô phỏng và sửa lỗi cơ bản sẽ được trình bày ở bài tiếp theo.

Thứ Hai, 18 tháng 9, 2017

[Basic Knowledge][Bài 3] Hướng dẫn phân tích thiết kế lõi IP step-by-step - Phân tích mạch nguyên lý

Bạn đang muốn bắt tay vào thiết kế một lõi IP (IP core) nhưng không biết bắt đầu từ đâu? Bài viết này là một phần trong chuỗi bài viết hướng dẫn bạn đi từng bước để có thể thiết kế được một lõi IP hoàn chỉnh. Phương pháp được sử dụng ở đây là top-down, phân tích từ tổng quan đến chi tiết. Ví dụ được trình bày là một thiết kế CPU cơ bản (simple CPU).

Trong bài 1 và bài 2, chúng ta đã hiểu đến bước phân tích chi tiết từ khối trong một thiết kế vi mạch. Bài này sẽ thực hiện lý thuyết đã trình bày trong bài 2 trên ví dụ CPU 8 bit đơn giản tên SCPU.
1. Tổng quan
Ở hai bài trước, chúng ta đã thực hiện qua các bước sau:
  1. Nghiên cứu và tìm hiểu
  2. Phân tích tổng quan
  3. Phân tích chi tiết
    1. Tìm các tín hiệu giao tiếp giữa các khối chức năng
Trong bài này, chúng ta tiếp tục thực hiện bước 2 của "phân tích chi tiết" là "phân tích cấu trúc" cho từng khối chức năng. Việc phân tích cấu trúc thực chất là tìm sự phụ thuộc của ngõ ra (output) theo các ngõ vào (input). "Sự phụ thuộc" này có thể được thể hiện bằng 1 trong các cách như đã trình bày ở bài 2 nhưng trong bài này chỉ sử dụng mạch nguyên lý mức cổng logic để thể hiện.
Để phân tích một ngõ ra, một số vấn đề cần được quan tâm:
  1. Tín hiệu ngõ là từ mạch tổ hợp hay tuần tự?
    1. Nếu là mạch tuần tự thì có cần reset không?
      1. Nếu có reset thì loại reset là đồng bộ hay bất đồng bộ?
      2. Giá trị reset là bao nhiêu?
    2. Nếu là mạch tổ hợp thì chỉ cần chuyển xuống câu hỏi 2 và 3
  2. Tín hiệu ngõ ra có thể mang những giá trị nào?
  3. Tín hiệu nào quyết định giá trị của ngõ ra?
Xét lại ví dụ về CPU 8 bit SCPU, hiện tại chúng ta có được các thông tin quan trọng sau đây.
Hình 1. Hoạt động của các lệnh SCPU
Hình 2. Sơ đồ khối SCPU

Hình 3. Sơ đồ giao tiếp của các khối
Trước khi vào phân tích cấu trúc từng khối, các bạn nên xem lại phần tìm tín hiệu giao tiếp giữa các khối của bài 2 để hiểu chức năng của từng tín hiệu.
2. Phân tích chi tiết khối FETCH

Như đã trình bày, việc phất tích chi tiết cấu trúc một khối chức năng là tìm mạch nguyên lý cho từng ngõ ra theo các ngõ vào.
Khối FETCH có 2 tín hiệu ngõ ra, fetch_ir[7:0] và fetch_dr[7:0], nên chỉ cần tìm mạch nguyên lý cho 2 tin hiệu này là kết thúc bước phân tích cấu trúc của khối FETCH.

2.1 Phân tích tín hiệu fetch_ir[7:0]
Tín hiệu này cung cấp giá trị mã lệnh cho khối DECODER. Nó được lấy từ một thanh ghi và là mạch tuần tự. Giá trị của tín hiệu giải mã các tín hiệu điều khiển nên cần reset. Giá trị reset của thanh ghi sẽ là mã lệnh NOP, b0111_0000. Trong thiết kế này, tín hiệu reset rst_n sẽ tích cực mức thấp và là reset đồng bộ. Tín hiệu chỉ lấy giá trị từ ngõ ra MEMORY. Tín hiệu dc_load_ir sẽ quyết định khi nào nó được cập nhật giá trị mới.
Hình 4. Mạch nguyên lý tín hiệu fetch_ir[7:0]
Ở đây, dc_load_ir là ngõ vào khối FETCH nên không cần phân tích thêm. Tín hiệu mem_dout[7:0] là ngõ ra của bộ nhớ MEMORY. Do bộ nhớ này không có sẵn nên phần này cũng phân tích luôn mô hình bộ nhớ này để sử dụng. Hoạt động của bộ nhớ các bạn xem lại bài 1. Mô hình cấu trúc chi tiết của bộ nhớ như sau:
Hình 5. Cấu trúc chi tiết của khối bộ nhớ MEMORY
Khối MEMORY đơn giản gồm 3 phần:
  • write_decoder: giải mã tín hiệu ghi vào mảng ô nhớ
  • mem_array: mảng ô nhớ gồm 256 ô, mỗi ô 8 bit
  • read_encoder: lựa chọn giá trị đọc từ mảng ô nhớ bằng địa chỉ truy xuất
Trong đó:
  • Tín hiệu cho phép ghi mem_wr là dc_mem_wr, ngõ vào khối FETCH nên không cần phân tích thêm
  • Bus dữ liệu ghi vào MEMORYlà dc_rd[7:0] vì MEMORY chỉ được ghi bằng giá trị Rd trong lệnh SWR và SWI, ngõ vào khối FETCH nên không cần phân tích thêm
Địa chỉ truy xuất MEMORY mem_addr[7:0] có thể mang các giá trị sau:
  • Giá trị PC để truy xuất mã lệnh của chương trình
  • Giá trị thanh ghi DR đối với lệnh đọc bộ nhớ LWI và ghi bộ nhớ SWI sử dụng địa chỉ là IMM
  • Giá trị thanh ghi Rs đối với lệnh đọc bộ nhớ LWR và ghi bộ nhớ SWR sử dụng địa chỉ là giá trị thanh ghi Rs
Tín hiệu để lựa chọn giá trị địa chỉ của MEMORY là dc_addr_sel[1:0].
Hình 6. Mạch nguyên lý tạo địa chỉ truy xuất MEMORY
Trong mạch tạo tín hiệu mem_addr[7:0], pc[7:0] và fetch_dr[7:0] không phải là ngõ vào khối FETCH.
pc[7:0] là một thanh ghi có giá trị reset ban đầu là 0 để chỉ đến ô nhớ bắt đầu chương trình. pc[7:0] được cập nhật giá trị mới khi dc_load_pc tích cực và giá trị cập nhật là giá trị IMM chứa trong thanh ghi DR hoặc giá trị sau khi tăng 1 đơn vị.
Hình 7. Mạch nguyên lý thanh ghi pc[7:0]
fetch_dr[7:0] là thành ghi DR dùng để lưu giá trị IMM được đọc từ bộ nhớ trong các lệnh có IMM. Thanh ghi này cập nhật giá trị mới khi dc_load_dr tích cực và không cần reset vì đây là thanh ghi không dùng để tạo tín hiệu điều khiển.
Hình 8. Thanh ghi DR
Đến đây, có thể thấy ngõ ra fetch_ir[7:0] được phân tích ban đầu đã có mối liên hệ hoàn toàn theo các ngõ vào, nghĩa là không có tín hiệu nào là chưa xác định được sự phụ thuộc theo ngõ vào. Quá trình phân tích cho fetch_ir[7:0] kết thúc.

 2.2 Phân tích tín hiệu fetch_mem_dout[7:0]

Tín hiệu này chính là ngõ ra của bộ nhớ mem_dout[7:0].
Hình 9. Tín hiệu fetch_mem_dout[7:0]
Quá trình phân tích cấu trúc khối FETCH kết thúc vì tất cả các ngõ ra đã được xác định theo các ngõ vào
3. Phân tích chi tiết khối DECODER

Tương tự với khối DECODER, từng ngõ ra sẽ được phân tích chi tiết. Bạn có thể chọn một ngõ ra bất kỳ để thực hiện. Ở đây, các ngõ ra sẽ được chọn theo thứ tự từ trên xuống như hình sơ đồ tín hiệu đã minh họa.

3.1 Phân tích tín hiệu dc_load_pc
Theo bảng phân tích hoạt động của các lệnh thì PC được cập nhật ở chu kỳ 1 và chu kỳ 3 của một lệnh. Để xác định các chu kỳ hoạt động, một bộ đếm chu kỳ sẽ được sử dụng, gọi là ctrl_counter[1:0]. Chu kỳ 1 tương ứng với bộ đếm bằng 0, chu kỳ 2 bộ đếm bằng 1 và chu kỳ 3 bộ đếm bằng 2.
Hình 10. Bộ đếm số chu kỳ hoạt động của lệnh ctrl_counter[1:0]
Thời điểm xóa bộ đếm về 0 phụ thuộc vào lệnh đó là lệnh nào, lệnh NOP là lệnh 1 chu kỳ, nhóm lệnh không có IMM là 2 chu kỳ và nhóm lệnh có IMM là 3 chu kỳ.
dc_load_pc sẽ tích cực khi bộ đến bằng 0 hoặc bằng 2, tức là khác 1.
Hình 11. Tín hiệu dc_load_pc
3.2 Phân tích tín hiệu dc_imm
Tín hiệu này được sử dụng để chọn giá trị mà PC sẽ được cập nhật. Đối với các lệnh nhảy, ở chu kỳ thứ 3, PC có thể được nạp giá trị bằng giá trị IMM chứa trong thanh ghi DR. Như vậy, tín hiệu này sẽ hoạt động ở chu kỳ thứ 3 và tùy vào điều kiện của từng lệnh nhảy để tích cực phù hợp.
Hình 12. Mạch nguyên lý tín hiệu dc_imm
3.3 Phân tích tín hiệu dc_addr_sel[1:0]
Đây là tín hiệu chọn địa chỉ cho MEMORY khối FETCH. Đối với lệnh LWR và SWR, ở chu kỳ 2, địa chỉ của MEMORY sẽ là giá trị thanh ghi Rs. Đối với lệnh LWI và SWI, ở chu kỳ 3, thì địa chỉ của MEMORY sẽ là giá trị thanh ghi DR. Tận dụng giá trị ctrl_counter[1:0] để thực hiện mạch cho tín hiệu dc_addr_sel[1:0]. Trường hợp còn lại, địa chỉ MEMORY luôn là PC.
Hình 13. Mạch nguyên lý tín hiệu dc_addr_sel[1:0]
3.4 Phân tích tín hiệu dc_rs[7:0] và dc_rd[7:0]
Đây là hai tín hiệu được lựa chọn từ 4 thanh ghi R0, R1, R2 và R3 dựa trên giá trị trường Rs và Rd của mã lệnh.
Hình 14. Mạch nguyên lý của tín hiệu dc_rs[7:0] và dc_rd[7:0]
Trong đó, các thanh ghi R0, R1, R2 và R3 có mạch nguyên lý như sau:
Hình 15. Mạch nguyên lý của các thanh ghi R0, R1, R2 và R3
3.5 Phân tích tín hiệu dc_mem_wr
Đây là tín hiệu cho phép ghi vào MEMORY ở khối FETCH. Tín hiệu chỉ tích cực ở lệnh SWR, chu kỳ 2 và lệnh SWI, chu kỳ 3.
Hình 16. Tín hiệu dc_mem_wr

3.6 Phân tích tín hiệu dc_load_dr
Đây là tín hiệu cho phép nạp thanh ghi DR, xảy ra ở chu kỳ 2 của các lệnh có IMM.
Hình 17. Mạch nguyên lý tín hiệu dc_load_dr

3.7 Phân tích tín hiệu dc_load_ir
Tín hiệu này chỉ tích cực ở chu kỳ thứ nhất để nạp thanh ghi IR.
Hình 18. TÍn hiệu dc_load_ir

3.8 Phân tích tín hiệu dc_op[1:0]
Với chức năng lựa chọn toán tử thực thi ở khối EXECUTE. Dựa vào bảng mã lệnh, toán tử chỉ có 4 loại là AND, OR, ADD và SUB. Hai bit thấp của opcode sẽ được sử dụng để tạo tín hiệu này.
Hình 19. Tín hiệu lựa chọn toán tử cho khối EXECUTE
4. Phân tích chi tiết khối EXECUTE

Khối EXCUTE chỉ chứa một ALU thực hiện bốn phép tính AND, OR, ADD và SUB. Khối này chỉ có một tín hiệu ngõ ra là kết quả của các phép tính trên.
HÌnh 20. Mạch nguyên lý tín hiệu ex_out[7:0]
Việc phân tích cấu trúc chi tiết từng khối của CPU 8 bit SCPU đã hoàn thành. Bước tiếp theo là dựa trên các mạch nguyên lý để mô tả RTL code. Phần này sẽ được trình bày cụ thể trong bài tiếp theo

**Mọi ý kiến trao đổi và góp ý xin comment dưới bài viết.