NỘI DUNG
> Bo mạch FPGA dùng cho kiểm chứng mạch điện tử số
1. Mục tiêu
Đề xuất thiết kế mức RTL, mô hình hóa bằng VHDL, mô phỏng bằng ModelSIM và thực thi trên FPGA một bộ vi xử lý 16-bit đơn giản. Ứng dụng bộ xử lý để xây dựng một hệ thống SoC ứng dụng trong điều khiển.
1. Giới thiệu
Bộ xử lý đa năng (General-Purpose Processor) đôi khi được gọi là bộ vi xử lý (microprocessor) hay đơn vị xử lý trung tâm CPU (Central Processing Unit) là một hệ thống số có thể lập trình nhằm giải quyết các tác vụ tính toán trong rất nhiều ứng dụng. Cùng một bộ xử lý có thể giải quyết các vấn đề tính toán trong rất nhiều ứng dụng khác nhau như hệ thống nhúng trong truyền thông, công nghiệp, ô-tô… Việc chọn sử dụng bộ xử lý đa năng để triển khai một phần chức năng của hệ thống có thể giúp người thiết kế đạt được một số lợi ích. Đầu tiên, giá thành của một đơn vị bộ vi xử lý có thể rất thấp, thường chỉ dưới một vài đô la. Một lý do cho chi phí thấp này là nhà sản xuất bộ xử lý có thể phân bổ chi phí NRE (Non-recurring engineering) cho nghiên cứu phát triển bộ xử lý trên một số lượng lớn các bộ vi xử lý được bán ra – thường có số lượng hàng triệu hoặc hàng tỷ đơn vị. Ví dụ, Motorola đã bán được gần nửa tỷ bộ vi điều khiển 68HC05 chỉ trong năm 1996 (nguồn: Báo cáo thường niên của Motorola 1996).
Bộ xử lý được cấu thành từ hai thành phần chính là đơn vị xử lý dữ liệu (datapath) và đơn vị điều khiển (control unit). Bộ vi xử lý thực hiện một chức năng mong muốn bằng cách nạp (fetch) và thực thi (execute) các lệnh (instruction) lưu sẵn trong bộ nhớ vì thế trong mô hình ứng dụng các vi xử lý thường gắn liền với một khối bộ nhớ để lưu chương trình và dữ liệu như chỉ ra trong Hình 1.
Hình 1: Cấu trúc các khối chức năng cơ bản của bộ vi xử lý.
1.1. Đơn vị điều khiển
Đơn vị điều khiển: bao gồm các mạch điện tử thực hiện chức năng nạp và giải mã các lệnh chương trình sau đó tạo ra các tín hiệu điều khiển quá trình truyền dữ liệu qua datapath theo các lệnh đó. Bộ điều khiển bao gồm một bộ đếm chương trình PC (Programmer Counter) dùng để lưu địa chỉ của ô nhớ chứa lệnh chương trình đầu tiên cần phải nạp vào vi xử lý. Bộ điều khiển cũng chứa một thanh ghi lệnh IR (Instruction Register) để lưu các lệnh được nạp. Bộ điều khiển sẽ giải mã các lệnh trong thanh ghi IR và tạo ra các ra các tín hiệu phù hợp để điều khiển luồng dữ liệu được xử lý trong datapath. Luồng dữ liệu này có thể là nạp dữ liệu từ hai thanh ghi toán hạng vào đơn vị ALU, lưu kết quả tính toán từ ALU vào một thanh ghi, hoặc di chuyển dữ liệu giữa bộ nhớ và một thanh ghi. Cuối cùng đơn vị điều khiển phải xác định giá trị tiếp theo của thanh ghi PC tương ứng với địa chỉ ô nhớ tiếp theo nào sẽ được nạp vào vi xử lý để thực hiện. Nếu lệnh hiện tại không phải lệnh rẽ nhánh thì bộ điều khiển sẽ tăng PC để trỏ tới địa chỉ của lệnh tiếp theo. Nếu lệnh hiện tại là lệnh rẽ nhánh thì bộ điều khiển cần dựa vào các tín hiệu trạng thái và thanh ghi IR để xác định địa chỉ lệnh tiếp theo cần nạp cho PC.
Độ rộng bit của thanh ghi PC xác định kích thước địa chỉ của bộ xử lý. Lưu ý rằng kích thước địa chỉ là độc lập với kích thước dữ liệu. Nói chung, kích thước địa chỉ thường lớn hơn kích thước dữ liệu. Kích thước địa chỉ xác định số lượng phần tử bộ nhớ mà vi xử lý có thể truy xuất trực tiếp. Tổng số lượng phần tử bộ nhớ mà vi xử lý có thể truy xuất cũng thường được gọi là không gian địa chỉ hay không gian bộ nhớ vật lý mà vi xử lý có thể quản lý. Nếu kích thước địa chỉ là M thì không gian địa chỉ là 2M. Do đó, một bộ vi xử lý với thanh ghi PC độ rộng 16-bit có thể định địa chỉ trực tiếp 216 = 64.536 phần tử nhớ. Chúng ta thường tham chiếu tới không gian địa chỉ này là 64K, với 1K = 210 = 1024.
Quá trình thực hiện mỗi lệnh của vi xử lý thường tuần tự trải qua một số giai đoạn chẳng hạn như nạp lệnh từ bộ nhớ, giải mã lệnh, nạp các toán hạng, thực thi lệnh trong datapath và lưu trữ kết quả. Mỗi giai đoạn có thể thực hiện trong một hoặc nhiều chu kỳ xung nhịp. Một chu kỳ xung nhịp thường là thời gian dài nhất được đòi hỏi để dữ liệu di chuyển từ một thanh ghi này sang một thanh ghi khác. Đường dẫn đi qua datapath hoặc bộ điều khiển dẫn đến thời gian dài nhất này (ví dụ: từ thanh ghi trong datapath qua ALU và quay lại thanh ghi trong datapath) được gọi là đường dẫn tới hạn (Critical Path). Nghịch đảo của chu kỳ xung nhịp được gọi là tần số xung nhịp, được đo bằng số chu kỳ mỗi giây và có đơn vị là Hertz (Hz). Ví dụ: chu kỳ xung nhịp 10 nano giây tương ứng với tần số 1/(10×10-9) Hz hoặc 100 MHz. Đường dẫn tới hạn càng ngắn thì tần số xung nhịp càng cao. Chúng ta thường sử dụng tần số xung nhịp làm một thước đo để so sánh các bộ xử lý, đặc biệt là các phiên bản khác nhau của cùng một bộ xử lý. Tần số xung nhịp cao hơn hàm ý rằng quá trình thực hiện chương trình nhanh hơn (mặc dù điều này không luôn luôn đúng).
1.2. Đơn vị xử lý dữ liệu
Đơn vị xử lý dữ liệu (Datapath) bao gồm các mạch điện tử thực hiện chức năng chuyển đổi dữ liệu và lưu trữ dữ liệu tạm thời. Đơn vị xử lý dữ liệu chứa một đơn vị logic số học (ALU: arithmetic-logic unit) có khả năng biến đổi dữ liệu thông qua thực hiện các phép tính như phép cộng, phép trừ, phép AND logic, phép OR logic, phép nghịc đảo và phép dịch bit. ALU cũng chịu trách nhiệm tạo ra các tín hiệu trạng thái, thường được lưu trữ trong một thanh ghi trạng thái, cho biết các trạng thái dữ liệu cụ thể. Các trạng thái như vậy bao gồm dữ liệu có bằng không hay không hoặc việc cộng hai hạng mục dữ liệu có tạo ra cờ nhớ hay không. Đơn vị xử lý dữ liệu cũng chứa các thanh ghi dùng để lưu trữ dữ liệu tạm thời. Dữ liệu tạm thời có thể bao gồm dữ liệu được đưa vào từ bộ nhớ nhưng chưa được gửi qua ALU, dữ liệu xuất ra từ ALU nhưng được dùng cho các hoạt động ALU sau này hoặc sẽ được gửi trở lại bộ nhớ, và dữ liệu phải được di chuyển từ vị trí bộ nhớ này sang vị trí bộ nhớ khác. Dữ liệu di chuyển trong datapath bus dữ liệu nội bộ, trong khi dữ liệu được di chuyển giữa vi xử lý và bộ nhớ dữ liệu thông qua bus dữ liệu ngoài.
Chúng ta thường phân biệt các bộ xử lý theo kích thước của chúng, trong đó kích thước thường được đo bằng độ rộng bit (hay số lượng các bit) của các thành phần cấu thành nên datapath. Một bit, viết tắt của binary digit (chữ số nhị phân), là đơn vị dữ liệu cơ bản của bộ xử lý, đại diện cho 0 (thấp hoặc sai) hoặc 1 (cao hoặc đúng), trong khi chúng ta gọi một nhóm 8 bit là một byte. Bộ xử lý N-bit có thể có các thanh ghi rộng N-bit, ALU rộng N-bit, bus nội bộ rộng N-bit và bus ngoài rộng N-bit. Vi xử lý thường có kích thước phổ biến là 4-bit, 8-bit, 16-bit, 32-bit và 64-bit. Tuy nhiên, trong một số trường hợp, một bộ xử lý cụ thể có thể có các kích thước khác nhau giữa các thanh ghi, ALU, bus bên trong hoặc bus ngoài. Ví dụ, bộ vi xử lý có thể có bus nội bộ, ALU và các thanh ghi kích thước 16-bit, nhưng chỉ có một bus ngoài 8-bit để giảm các chân trên IC của bộ xử lý. Do đó, định nghĩa kích thước của bộ xử lý không phải là một khái niệm chính xác tuyệt đối.
1.3. Kiến trúc tập lệnh
Người thiết kế vi xử lý hoặc lập trình vi xư lý mức lệnh hợp ngữ cần biết tập lệnh (Instruction Set Architecture) của vi xử lý. Lý tưởng nhất, lập trình viên sử dụng ngôn ngữ cấu trúc sẽ không cần biết tập lệnh của bộ xử lý. Tuy nhiên, gần như mọi hệ thống nhúng đều yêu cầu lập trình viên viết ít nhất một phần của chương trình bằng ngôn ngữ hợp ngữ. Các phần chương trình đó có thể thực hiện các hoạt động vào/ra mức thấp với các thiết bị bên ngoài bộ xử lý chẳng hạn như bàn phím, thiết bị hiển thị. Một thiết bị như vậy có thể yêu cầu các chuỗi tín hiệu được định thời gian cụ thể để nhận dữ liệu và lập trình viên có thể thấy rằng việc viết mã bằng hợp ngữ đạt được sự định thời gian như vậy một cách thuận tiện nhất. Một ví dụ là trình điều khiển thiết bị (driver) là một phần của chương trình được viết một cách đặc biệt để truyền thông hoặc vận hành một thiết bị khác. Vì các trình điều khiển thường được viết bằng hợp ngữ, lập trình viên sử dụng ngôn ngữ có cấu trúc vẫn có thể được yêu cầu biết ít nhất một tập hợp con của tập lệnh.
Tập lệnh mô tả các cấu hình mức nhị phân (bit) của một lệnh mà vi xử lý có thể hiểu và do đó cho biết các hoạt động hạt nhân của bộ xử lý mà lập trình viên có thể gọi. Mỗi cấu hình như vậy tạo thành một lệnh hợp ngữ và một chuỗi các lệnh đó tạo thành một chương trình hợp ngữ. Một lệnh thường có hai phần, trường mã lệnh opcode và trường toán hạng. Một opcode xác định hoạt động diễn ra trong suốt quá trình thực thi một lệnh. Chúng ta có thể phân loại các lệnh thành ba loại.
- Lệnh truyền dữ liệu thực hiện các thao tác liên quan đến việc di chuyển dữ liệu giữa bộ nhớ và các thanh ghi, giữa các kênh đầu vào/đầu ra và các thanh ghi và giữa các thanh ghi.
- Các lệnh số học/logic cấu hình ALU để thực hiện một chức năng cụ thể, tạo các kênh truyền dữ liệu từ các thanh ghi tới ALU và các kênh truyền dữ liệu từ ALU trở lại một thanh ghi cụ thể.
- Các lệnh rẽ nhánh xác định địa chỉ của lệnh chương trình tiếp theo sẽ được nạp và thực thi trên datapath dựa trên các tín hiệu trạng thái datapath. Các lệnh rẽ nhánh có thể được phân loại thành các lệnh nhảy không điều kiện, các lệnh nhảy có điều kiện hoặc các lệnh gọi thủ tục và trở về từ thủ tục. Lệnh nhảy không điều kiện luôn xác định địa chỉ của lệnh tiếp theo trong lệnh, trong khi nhảy có điều kiện chỉ làm như vậy nếu một số điều kiện được đánh giá là đúng, chẳng hạn như một thanh ghi xác định nào đó chứa giá trị 0. Một lệnh gọi chương trình con ngoài việc chỉ ra địa chỉ của lệnh tiếp theo còn phải lưu địa chỉ của lệnh hiện tại để lệnh quay lại từ chương trình con có thể quay lại lệnh ngay sau lệnh gọi chương trình được gọi gần đây nhất. Cặp lệnh này giúp việc thực hiện các lệnh gọi thủ tục hoặc hàm trong các chương trình viết bằng ngôn ngữ lập trình bậc cao diễn ra dễ dàng hơn.
Trường toán hạng (operand field) xác định vị trí của dữ liệu thực tế được sử dụng trong quá trình thực thi lệnh. Toán hạng nguồn đóng vai trò là đầu vào cho hoạt động, trong khi toán hạng đích lưu trữ kết quả đầu ra. Số lượng toán hạng trên mỗi lệnh là khác nhau giữa các bộ xử lý. Ngay cả đối với cùng một bộ xử lý nhất định, số lượng toán hạng trên mỗi lệnh cũng có thể thay đổi tùy theo loại lệnh.
Trường toán hạng có thể chỉ ra vị trí của dữ liệu thông qua một trong số các chế độ địa chỉ như được minh họa trong Hình 2. Trong chế độ định địa chỉ tức thì (Immediate), trường toán hạng chứa bản thân dữ liệu cần truy xuất. Trong chế độ định địa chỉ trực tiếp thanh ghi (Register-direct), trường toán hạng chứa địa chỉ của thanh ghi trong datapath mà dữ liệu được lưu trong đó. Trong chế độ định địa chỉ gián tiếp thanh ghi (Register-Indirect), trường toán hạng chứa địa chỉ của thanh ghi mà nội dung của nó chứa địa chỉ của một vị trí bộ nhớ chứa dữ liệu. Trong chế độ định địa chỉ trực tiếp (Direct), trường toán hạng chứa địa chỉ của một vị trí bộ nhớ chứa dữ liệu thực sự cần truy xuất. Trong chế độ định địa chỉ gián tiếp (Indirect), trường toán hạng chứa địa chỉ của một vị trí bộ nhớ mà nội dung chứa địa chỉ của một vị trí bộ nhớ thực sự chứa dữ liệu. Trong ngữ cảnh của các ngôn ngữ lập trình cấu trúc thì các chế độ định địa chỉ trực tiếp thực hiện các biến thông thường và các chế độ định địa chỉ gián tiếp thực hiện các con trỏ. Ngoài ra còn một số chế độ định địa chỉ khác không được liệt kê trong Bảng 1 như chế độ định địa chỉ ẩn, chế độ định địa chỉ chỉ số, chế độ định địa chỉ tương đối. Trong chế độ định địa chỉ địa ẩn (implicit addressing), một thanh ghi hoặc vị trí bộ nhớ của dữ liệu được ẩn trong mã lệnh opcode, chẳng hạn dữ liệu có thể lưu trong thanh ghi tích lũy (accumulator). Trong chế độ định địa chỉ chỉ số (indexed addressing), toán hạng trực tiếp hoặc gián tiếp phải được cộng thêm vào một thanh ghi ngầm định cụ thể để có được địa chỉ toán hạng thực tế. Các lệnh nhảy có thể sử dụng chế độ định địa chỉ tương đối để giảm số lượng bit cần thiết để chỉ ra địa chỉ bước nhảy. Một địa chỉ tương đối cho biết khoảng cách nhảy từ địa chỉ hiện tại, thay vì chỉ ra địa chỉ đầy đủ – chế độ định địa chỉ này rất phổ biến vì hầu hết các bước nhảy là tới lệnh gần đó.
Hình 2. Các chế độ định địa chỉ.
1.4. Nguyên lý hoạt động
1.4.1. Quá trình thực thi lệnh
Chúng ta có chia quá trình thực hiện các lệnh của vi xử lý thành một số giai đoạn cơ bản như sau:
- Tìm nạp lệnh (Fetch Instruction): có nhiệm vụ đọc lệnh tiếp theo từ bộ nhớ vào thanh ghi lệnh.
- Giải mã lệnh (Decode Instruction): có nhiệm vụ xác định lệnh trong thanh ghi lệnh cần thực hiện thao tác nào (ví dụ: cộng, di chuyển dữ liệu, v.v.).
- Tìm nạp các toán hạng (Fetch Operands): có nhiệm vụ di chuyển dữ liệu toán hạng của lệnh vào các thanh ghi thích hợp.
- Thực thi thao tác (Execute operation): nhiệm vụ thực hiện thao tác trên các thanh ghi thích hợp bằng ALU và ghi kết quả vào một thanh ghi thích hợp.
- Lưu trữ kết quả (Store Resutls): có nhiệm vụ lưu trữ nội dung một thanh ghi vào bộ nhớ.
Nếu mỗi giai đoạn mất một chu kỳ xung nhịp, thì chúng ta có thể thấy rằng một lệnh đơn có thể mất vài chu kỳ để hoàn thành.
1.4.2. Đường ống hóa quá trình thực thi lệnh
Đường ống hóa quá trình thực thi lệnh (Pipelining) là một kỹ thuật phổ biến để tăng thông lượng thực thi lệnh của bộ vi xử lý. Đầu tiên để hiểu quá trình pipelining là gì chúng ta xem xét một tình huống tương tự trong cách hai người tiếp cận công việc rửa và sấy 8 cái đĩa ăn. Trong cách tiếp cận thứ nhất, người thứ nhất rửa xong tất cả 8 cái đĩa ăn và sau đó người thứ hai mới bắt đầu sấy 8 đĩa ăn này. Giả sử mỗi người hoàn thành rửa hoặc sấy một đĩa ăn trong một phút, phương pháp này cần 16 phút. Cách tiếp cận này rõ ràng là không hiệu quả vì tại một thời điểm bất kỳ chỉ có một người làm việc và người kia không làm gì. Một cách tiếp cận tốt hơn là cho người thứ hai bắt đầu sấy khô đĩa ăn đầu tiên ngay sau khi nó được rửa sạch. Cách tiếp cận này chỉ cần 9 phút – 1 phút cho đĩa ăn đầu tiên được rửa, và sau đó 8 phút nữa cho đến khi đĩa ăn cuối cùng được làm khô. Chúng ta gọi phương pháp thứ hai này là quá trình thực hiện công việc được đường ống hóa – pipelined.
Liên hệ tới quá trình thực thi lệnh của bộ vi xử lý có thể thấy mỗi đĩa ăn giống như một lệnh và hai nhiệm vụ rửa và sấy khô đĩa giống như năm giai đoạn thực thi lệnh được liệt kê ở trên. Bằng cách sử dụng một đơn vị phần cứng riêng biệt (mỗi đơn vị tương tự như một người) cho mỗi giai đoạn, chúng ta có thể đường ống hóa quá trình thực hiện lệnh. Sau khi đơn vị nạp lệnh tìm nạp lệnh đầu tiên, đơn vị giải mã sẽ giải mã nó trong khi đơn vị tìm nạp lệnh đồng thời tìm nạp lệnh tiếp theo. Ý tưởng về kỹ thuật đường ống hóa được minh họa trong Hình 3. Lưu ý rằng để đường ống hoạt động tốt, việc thực hiện lệnh phải được phân tách thành các giai đoạn có độ dài gần bằng nhau và mỗi lệnh phải yêu cầu cùng một số chu kỳ.
Hình 3. Kỹ thuật đường ống hóa: (a) quá trình rửa đĩa không pipeline, (b) quá trình rửa đĩa được pipeline, (c) quá trình thực thi lệnh được pipeline.
Các lệnh rẽ nhánh đặt ra một vấn đề cho kỹ thuật đường ống hóa vì chúng ta không biết lệnh tiếp theo cho đến khi lệnh hiện tại đã đạt đến giai đoạn thực thi. Một giải pháp là trì hoãn hoạt động nạp lệnh của đường ống khi có một lệnh rẽ nhánh đang nằm trong đường ống, chờ lệnh rẽ nhánh được thực hiện ở giai đoạn thực thi trước khi tìm nạp lệnh tiếp theo. Một cách khác là đoán trước lệnh rẽ nhánh sẽ đi theo hướng nào và tìm nạp lệnh tương ứng tiếp theo. Nếu phán đoán là đúng, quá trình thực thi lệnh được tiến hành liên tục mà không có sự trừng phạt. Nhưng nếu trong giai đoạn thực thi chỉ ra phán đoán là sai, tất cả các lệnh đã được nạp sau lệnh rẽ nhánh phải được loại bỏ, do đó quá trình thực thi lệnh phải chịu sự trừng phạt. Các bộ vi xử lý hiện đại thường có các bộ tiên đoán rẽ nhánh rất tinh vi được tích hợp sẵn.
2. Yêu cầu thiết kế
2.1. Kiến trúc tập lệnh
Bộ vi xử lý có khả năng thực hiện 10 lệnh 16-bit cơ bản như được liệt kê trong Bảng 1. Tập lệnh bao gồm bốn lệnh di chuyển dữ liệu, 4 lệnh số học/logic và hai lệnh rẽ nhánh. Hình 4(a) chỉ ra một chương trình được viết bằng ngôn ngữ C thực hiện chức năng cộng tích lũy các số từ 1 đến 10. Hình 4(b) chỉ ra một chương trình hợp ngữ thực hiện cùng một chức năng như vậy nhưng được viết bằng tập lệnh trong Bảng 1.
Bảng 1: Cấu trúc tập lệnh.
TT | Assembly Instruction | First Byte | Second Byte | Operation | ||
Opcode | Operand1 | Operand2 | ||||
1 | MOV Rn, direct | 0000 | Rn | direct | Rn = M(direct) | |
2 | MOV direct, Rn | 0001 | Rn | direct | M(direct) = Rn | |
3 | MOV Rn, @Rm | 0010 | Rn | Rm | M(Rm) = Rn | |
4 | MOV Rn, #immed | 0011 | Rn | immediate | Rn = immediate | |
5 | ADD Rn, Rm | 0100 | Rn | Rm | Rn = Rn + Rm | |
6 | SUB Rn, Rm | 0101 | Rn | Rm | Rn = Rn – Rm | |
7 | JZ Rn, Addr | 0110 | Rn | Addr | PC = Addr only if Rn = 0 | |
8 | OR Rn, Rm | 0111 | Rn | Rm | Rn = Rn OR Rm | |
9 | AND Rn, Rm | 1000 | Rn | Rm | Rn = Rn AND Rm | |
10 | JMP Addr | 1010 | Rn | Adrr | PC = Addr |
int total = 0;
for (int i=10; i!=0; i–) total += i; // next instructions… |
MOV R0, #0; // total = 0
MOV R1, #10; // i = 10 MOV R2, #1; // constant 1 MOV R3, #0; // constant 0 Loop: JZ R1, Next; // Done if i=0 ADD R0, R1; // total += i SUB R1, R2; // i– JMP Loop; // Jump always to Loop
Next: // next instructions… |
(a) | (b) |
Hình 4. Chương trình mẫu: (a) mã nguồn C, (b) mã nguồn hợp ngữ.
2.2. Kiến trúc vi xử lý
Cấu trúc của bộ vi xử lý bao gồm các khối chức năng như trong Hình 1, trong đó:
- Đơn vị điều khiển (Control Unit) có:
- Thanh ghi PC (Program Counter): 16-bit, dùng để chứa địa chỉ của lệnh tiếp theo mà bộ vi xử lý sẽ thực hiện
- Thanh ghi IR (Instruction Register): 16-bit, dùng để chứa lệnh mà vi xử lý sẽ thực hiện
- Controller: điều khiển quá trình đọc lệnh từ bộ nhớ vào thanh ghi IR sau đó tiến hành giải mã lệnh và tạo ra các tín hiệu điều khiển hoạt động của datapath nhằm thực hiện lệnh được nạp trong IR.
- Đơn vị xử lý dữ liệu (Datapath):
- Tệp thanh ghi RF (Register File): 16×16 bit, dùng lưu dữ liệu trong quá trình tính toán của ALU
- ALU (arithmetic and logic unit): hỗ trợ các phép tính logic và số học trên dữ liệu 16-bit như chỉ ra trong tập lệnh Bảng 1
- Bộ nhớ Memory: 64K×16-bit, dùng để lưu chương trình và dữ liệu cho bộ vi xử lý
3. Thiết kế mức RTL của bộ vi xử lý
Chúng ta áp dụng «quy trình các bước thiết kế mạch tích hợp» để đưa ra kiến trúc mức RTL của bộ vi xử lý. Vì bộ vi xử lý không thực hiện bất kỳ một thuật toán nào cụ thể mà chỉ thực thi một tập hợp các lệnh Instruction cho trước nên chúng ta sẽ bắt đầu quá trình thiết kế bằng việc trực tiếp đưa ra mô hình máy trạng thái phức hợp FSMD (FSM with Datapath) cho bộ vi xử lý. Từ mô hình FSMD chúng ta xây dựng cấu trúc đơn vị xử lý dữ liệu – datapath sau đó xây dựng máy trạng thái FSM bằng cách loại bỏ các phần tính toán đã được thực hiện bởi Datapath ra khỏi FSMD. Phần FSM chính là mô hình mô tả hoạt động của khối điều khiển (Controller) trong Hình 1. Sau khi có được mô hình máy trạng thái FSM và kiến trúc Datapath chúng ta có thể tiến hành mô tả chúng bằng VHDL để có thể thực hiện việc mô phỏng kiểm chứng hoạt động chức năng của bộ vi xử lý trên máy tính.
3.1. Đề xuất FSMD
Hoạt động của bộ vi xử được mô hình hóa bằng một máy trạng thái phức hợp như trong Hình 5. Sở dĩ được gọi là máy trạng thái phức hợp bởi vì nó bao hàm không chỉ phần điều khiển (control tương ứng với máy trạng thái FSM) mà còn cả phần cứng thực hiện các phép tính và xử lý dữ liệu (Datapath).
Hình 5: Máy trạng thái FSMD.
3.2. Cấu trúc đơn vị xử lý dữ liệu Datapath
Trong bộ vi xử lý, đơn vị xử lý dữ liệu Datapath thực hiện tất cả phép tính được yêu cầu bởi bộ vi xử lý. Datapath thường được cấu thành bởi các mạch để thực hiện các toán tử số học (cộng, trừ, nhân, so sánh …) và các phép tính logic (and, or, ..), các bộ hợp kênh và phân kênh để điều hướng luồng dữ liệu, các phần tử nhớ (thường là các thanh ghi) để lưu dữ liệu và kết quả trung gian trong quá trình vi xử lý hoạt động. Kiến trúc Datapath và các mô-đun thành phần của bộ vi xử cần thiết kế được chỉ ra trong các Hình 6, Hình 7 và Hình 8.
Hình 6: Cấu trúc Datapath.
Hình 7. Cấu trúc RTL của ALU.
Bảng 2. Các phép tính sồ học và logic được thực hiện bởi ALU.
ALUs | ALUr |
00 | OPr1 + OPr2 |
01 | OPr1 – Opr2 |
10 | OPr1 OR OPr2 |
11 | OPr1 AND OPr2 |
Hình 8. Cấu trúc RTL của tệp thanh ghi RF.
3.3. Đơn vị điều khiển Control Unit
3.3.1. Khối điều khiển Controller
Hình 9: Giao diện ghép nối vào/ra của controller.
Hình 10: Mô hình máy trạng thái FSM của bộ điều khiển.
3.3.2. Thanh ghi bộ đếm chương trình
Hình 11. Giao diện vào ra.
Bảng 3: Bảng chân lý.
PCclr | PCld | PCincr | CLK | PC_in | PC_out |
‘1’ | X | X | X | X | 0 |
‘0’ | 1 | X | ↑ | – | PC_in |
‘0’ | ‘0’ | ‘1’ | ↑ | X | PC_out + 1 |
‘0’ | ‘0’ | ‘0’ | ↑ | X | PC_out |
3.3.3. Thanh ghi lệnh
Bảng 4: Bảng chân lý.
IRclr |
IRld |
CLK |
IR_in |
IR_out |
‘1’ |
X |
X |
X |
0 |
‘0’ |
1 |
↑ |
– |
IR_in |
‘0’ |
‘0’ |
↑ |
X |
PC_out |
3.4. Kiến trúc tổng thể của bộ vi xử lý
Hình 12: Cấu trúc mức RTL hoàn thiện của bộ vi xử lý.
5. Mô hình hóa bộ vi xử lý bằng VHDL
Trong thư mục src chứa các file VHDL mô tả cpu và cpu_tb cùng với các khối chính tạo nên CPU. Riêng memory đã được hòan thiện cả phần mô tả thiết kế và testbench như một ví dụ mẫu.
Hình 13. Tổ chức cây của các tệp VHDL.
6. Kết quả mô phỏng
Hình 14. Mô hình testbench kiểm chứng chức năng của bộ vi xử lý.
— Machine code for initializing program memory
X”0911″, — 0: Mov R9,17 => R9 = M(17) X”1980″, — 1: Mov 128,R9 => M(128) = R9 X”3210″, — 2: MOV R2,#16 => R2 = 16 X”2290″, — 3: Mov R2,@R9 => M(R9) = R2 X”4920″, — 4: ADD R9,R2 => R9 = R9 + R2 X”5920″, — 5: SUB R9,R2 => R9 = R9 – R2 X”6201″, — 6: JZ R2,1 — for (iny i=10; i!=0;i–) total += 1; X”3000″, — 7: Start: Mov R0,#0 //total = 0 X”310A”, — 8: Mov R1,#10 //i=10 X”3201″, — 9: Mov R2,#1 //constant 1 X”3300″, — 10: Mov R3,#0 //constant 0 X”6110″, — 11: Loop: JZ R1,Next //Done if i=0 X”4010″, — 12: ADD R0,R1 //total +=i X”2090″, — 13: MOV R0,@R9 // M(R9) = R0 = total: display the total on GPIO X”5120″, — 14: SUB R1,R2 //i– X”630B”, — 15: JZ R3,Loop //total +=i X”6307″, — 16: Next JZ R3,Start //total +=i X”0100″ — 17: 0x100 // GPIO address ); |
6.1. Kết quả mô phỏng
Tại cửa sổ Transcript của ModelSIM lần lượt gõ các lệnh run như sau:
- VSIM> run 60
để kiểm tra hoạt động của tín hiệu Reset
- VSIM> run 410
để kiểm tra hoạt động của các lệnh
0: Mov R9,17 => R9 = M(17)
1: Mov 128,R9 => M(128) = R9
2: MOV R2,#16 => R2 = 16
- VSIM> run 120
để kiểm tra hoạt động của các lệnh
3: Mov R2,@R9 => M(R9) = R2
- VSIM> run 140
để kiểm tra hoạt động của các lệnh
4: ADD R9,R2 => R9 = R9 + R2
- VSIM> run 240
để kiểm tra hoạt động của các lệnh
5: SUB R9,R2 => R9 = R9 – R2
6: JZ R2,1
- VSIM> run 1020
để kiểm tra hoạt động của các lệnh của lần lặp thứ 1
7: Start: Mov R0,#0 //total = 0
8: Mov R1,#10 //i=10
9: Mov R2,#1 //constant 1
10: Mov R3,#0 //constant 0
11: Loop: JZ R1,Next //Done if i=0
12: ADD R0,R1 //total +=i
13: MOV R0,@R9 // M(R9) = R0 = total
14: SUB R1,R2 //i–
15: JZ R3,Loop //total +=i
16: Next JZ R3,Start //total +=i
Kết quả: Total = M(0x100) = 10 = 0xA
- VSIM> run 620
để kiểm tra hoạt động của các lệnh của lần lặp thứ 2
Kết quả: Total = M(0x100) = 10+9 = 19 = 0x13
- VSIM> run 620
để kiểm tra hoạt động của các lệnh của lần lặp thứ 3
Kết quả: Total = M(0x100) = 10+9+8 = 27 = 0x1B
- VSIM> run 620
để kiểm tra hoạt động của các lệnh của lần lặp thứ 4
Kết quả: Total = M(0x100) = 10+9+8+7 = 34 = 0x22
- VSIM> run 620
để kiểm tra hoạt động của các lệnh của lần lặp thứ 5
Kết quả: Total = M(0x100) = 10+9+8+7+6 = 40 = 0x28
- VSIM> run 620
để kiểm tra hoạt động của các lệnh của lần lặp thứ 6
Kết quả: Total = M(0x100) = 10+9+8+7+6+5 = 45 = 0x2D
- VSIM> run 620
để kiểm tra hoạt động của các lệnh của lần lặp thứ 7
Kết quả: Total = M(0x100) = 10+9+8+7+6+5+4 = 49 = 0x31
- VSIM> run 620
để kiểm tra hoạt động của các lệnh của lần lặp thứ 8
Kết quả: Total = M(0x100) = 10+9+8+7+6+5+4+3 = 52 = 0x34
- VSIM> run 620
để kiểm tra hoạt động của các lệnh của lần lặp thứ 9
Kết quả: Total = M(0x100) = 10+9+8+7+6+5+4+3+2 = 54 = 0x36
- VSIM> run 620
để kiểm tra hoạt động của các lệnh của lần lặp thứ 10
Kết quả: Total = M(0x100) = 10+9+8+7+6+5+4+3+2+1 = 55 = 0x37
Hình 16: Kết quả mô phỏng.
Video ghi hình quá trình chạy mô phỏng: https://youtu.be/xNiY9-oYz8c