Tự viết Emulator: CHIP-8 Interpreter (Phần 2)
Emulator
2
Giả lập
2
White

Huy Trần viết ngày 06/05/2016

Ở phần trước, chúng ta đã nắm được các khái niệm cơ bản cần có của một hệ thống CHIP-8. Phần này chúng ta sẽ tìm hiểu về cách hoạt động của trình thông dịch CHIP-8 và tập lệnh của nó.

Cấu trúc opcode

Như đã nói ở phần trước, một chương trình CHIP-8 là một tập hợp các opcode dưới dạng hexa. Ví dụ, đây là một đoạn nội dung khi đọc file rom game PONG:

alt text

Mỗi opcode có độ dài 2 byte và dược thể hiện bởi 4 kí tự hexa. Chúng ta sẽ đọc 2 byte này và chuyển về mã Assembly đơn giản để dễ hình dung, và công việc sẽ là: implement các chức năng mà các câu lệnh Assembly này thực hiện.

Để lưu vị trí của opcode hiện tại, chúng ta dùng một giá trị gọi là Program Counter (PC)

Ví dụ, lệnh đơn giản nhất là 0x00E0, gồm có 2 byte 00E0, dịch ra mã máy là CLS có nhiệm vụ xoá toàn bộ nội dung màn hình. Hay như hình trên, chúng ta có opcode là 0x82E4, trong đó có 2 giá trị x = 2y = E (sẽ giải thích bên dưới), dịch ra mã máy sẽ là ADD V2, VE, có nhiệm vụ cộng giá trị của thanh ghi VE vào V2 (V2 = V2 + VE).

Thứ tự cao thấp của các bit, byte

Mỗi byte có 8 bit, trong đó theo thứ tự từ phải qua trái, các bit sẽ được gọi tên từ thấp đến cao. Và opcode có 2 byte, như vậy là 16 bit, và theo thứ tự này, ta có cách gọi tên như hình sau:

alt text

Bit thấp là các bit về phía bên phải, và bit cao là các bit về phía bên trái. Bit cao nhất là bit thứ 1, bit thấp nhất là bit thứ 16.

Byte đầu tiên (1st Byte) được gọi là Byte cao (High byte). Byte thứ 2 (2nd byte) được gọi là Byte thấp (low byte)

Chúng ta có thể gọi mỗi 4 bit là một nibble.

Lọc giá trị bằng phép toán AND

Trong các phép toán thao tác bit, phép toán mà chúng ta cần sử dụng nhiều nhất trong quá trình viết Emulator là phép toán AND.

Chi tiết về phép toán AND bạn có thể xem tại: https://vi.wikipedia.org/wiki/Phép_toán_thao_tác_bit

Nếu dài quá nhác đọc thì phép toán này có thể tóm tắt như hình sau:

alt text

Một giá trị khi thực hiện AND với 0 thì trả về 0, và với F thì trả về chính nó. Chúng ta dùng đặc tính này để lọc và lấy ra các giá trị cần thiết tại các vị trí mong muốn bên trong một opcode.

Ví dụ:

alt text

Các tham số trong OPCODE

Ở ví dụ trên, ta thấy opcode 0x82E4 nhận vào 2 tham số x = 2y = E, có tất cả 4 loại tham số mà chúng ta cần lấy ra từ opcode, tuỳ theo từng loại/chức năng của opcode.

  • n: Tham số n4 bit cuối cùng (thấp nhất) của toàn bộ opcode, có thể lấy bằng cách sử dụng phép toán opcode & 0x000F:
  // Giả sử: opcode = 0x8C74
  var n = opcode & 0x000F;
  // Kết quả: n = 4
  • nnn: Tham số nnn12 bit thấp nhất của opcode, là kết quả của phép tính opcode & 0x0FFF:
  // Giả sử: opcode = 0x8C74
  var nnn = opcode & 0x0FFF;
  // Kết quả: nnn = C74
  • kk: Tham số kk8 bit thấp nhất của opcode, tính bằng: opcode & 0x00FF:
  // Giả sử: opcode = 0x8C74
  var kk = opcode & 0x00FF;
  // Kết quả: kk = 74
  • x: Tham số x được xác định bởi 4 bit thấp nhất của Byte cao trong opcode, tức là các bit 5, 6, 7, 8 . Như vậy, ta có thể thực hiện phép toán AND: opcode & 0x0F00 kết hợp phép toán dịch bit (shift) để tìm ra x:
  // Giả sử: opcode = 0x8C74
  var x = (opcode & 0x0F00) >> 8;
  // Kết quả của phép (opcode & 0x0F00) sẽ trả về 0x0C00
  // và ta cần dịch chuyển giá trị trên 8 bit về bên phải (>> 8)
  // để thu được giá trị 0x000C = C, là giá trị ta cần tìm 
  // Kết quả: x = C
  • y: Tham số y được xác định bằng 4 bit cao của Byte thấp trong opcode, tức là các bit 9, 10, 11, 12. Và ta có thể thực hiện phép toán opcode & 0x00F0shift để tìm y:
  // Giả sử: opcode = 0x8C74
  var y = (opcode & 0x00F0) >> 4;
  // Kết quả của (opcode & 0x00F0) là 0x0070, nên ta cần
  // dịch chuyển giá trị trên 4 bit về bên trái (>> 4)
  // để thu được giá trị 0x0007 = 7
  // Kết quả: y = 7

Tập lệnh của CHIP-8

Bây giờ, chúng ta sẽ cùng tìm hiểu chức năng của từng opcode. Chi tiết cách implement cho từng opcode sẽ có ở bài sau.

Nếu bạn thắc mắc là chúng ta cần biết chức năng của những lệnh này làm gì, thì: bằng cách implement từng lệnh riêng lẽ, chúng ta sẽ tạo được một trình thông dịch mà dựa vào những tập lệnh đó, các lập trình viên có thể kết hợp và viết thành một chương trình hoặc một trò chơi hoàn chỉnh.

Bạn sẽ cần tập lệnh này để tham khảo khi impement.

Các lệnh về xử lý logic

00E0 - CLS

Opcode có giá trị 0x00E0 có thể chuyển thành mã assembly tương ứng là CLS, có nhiệm vụ xoá toàn bộ mần hình.

1nnn - JP addr

Opcode có dạng 0x1nnn có mã assembly tương ứng là JP nnn, có nhiệm vụ đưa program counter đến địa chỉ nnn (tức là nhảy đến một đoạn nào đó trong chương trình)

2nnn - CALL addr

Opcode có dạng 0x2nnn có mã assembly tương ứng là CALL nnn, có nhiệm vụ gọi một subroutine (có thể hiểu là chương trình con) bắt đầu tại vị trí nnn. Vị trí hiện tại của progam counter trước khi thực hiện việc gọi subroutine sẽ được lưu vào stack

00EE - RET

Opcode có giá trị 0x00EE có mã assembly tương ứng là RET. Khi gặp lệnh này, interpreter sẽ đưa program counter về vị trí cuối cùng lưu trong stack (tức lf thoát khỏi sobroutine/chương trình con)

3xkk - SE Vx, byte

Gồm có 2 tham số xkk, có nhiệm vụ so sánh giá trị của Vxkk, nếu chúng bằng nhau thì bỏ qua (skip) lệnh tiếp theo bằng cách tăng giá trị của program counter lên 2.

4xkk - SNE Vx, byte

Tương tự như lệnh trên, nếu giá trị của Vx khác kk thì skip lệnh tiếp theo (tăng program counter lên 2)

5yx0 - SE Vx, Vy

So sánh giá trị của VxVy, nếu bằng nhau thì skip lệnh tiếp theo.

6xkk - LD Vx, byte

Gán giá trị của Vx thành kk

7xkk - ADD Vx, byte

Đặt giá trị của Vx bằng Vx + kk

8xy0 - LD Vx, Vy

Lưu giá trị của Vy vào Vx

8xy1 - OR Vx, Vy

Vx = Vx OR Vy

Thực hiện phép tính OR giữa 2 giá trị VxVy rồi lưu kết quả vào Vx

8xy2 - AND Vx, Vy

Vx = Vx AND Vy

Thực hiện phép tính AND giữa 2 giá trị VxVy rồi lưu kết quả vào Vx

8xy3 - XOR Vx, Vy

Vx = Vx XOR Vy

Thực hiện phép tính XOR giữa VxVy rồi lưu kết quả vào Vx

8xy4 - ADD Vx, Vy

Gán Vx = Vx + Vy, gán VF = carry (nhớ)

Giá trị của VxVy được cộng lại với nhau và lưu vào Vx, nếu kết quả lớn hơn 8 bit (vd: > 255) thì VF sẽ được đặt là 1, ngược lại sẽ là 0.

8xy5 - SUB Vx, Vy

Gán Vx = Vx - Vy, gán VF = NOT borrow (không mượn)

Nếu Vx > Vy hiệu số của Vx - Vy là không âm, nên VF sẽ được gán bằng 1, ngược lại thì bằng 0. Kết quả lưu vào Vx

8xy6 - SHR Vx {, Vy}

Gán Vx = Vx SHR 1

Nếu bit thấp nhất của Vx là 1 thì gán VF thàh 1, ngược lại thì gán bằng 0.
Gán Vx = Vx / 2

8xy7 - SUBN Vx, Vy

Gán Vx = Vy - Vx, gán VF = NOT borrow (không mượn)

Nếu Vy > Vx thì gán VF thành 1, ngược lại gán thành 0. Hiệu số lưu vào Vx.

8xyE - SHL Vx {, Vy}

Gán Vx = Vx SHL 1

Nếu bit cao nhất của Vx1 thì gán VF thành 1, ngược lại, gán thành 0. Cuối cùng gán Vx = Vx * 2

9xy0 - SNE Vx, Vy

Skip lệnh tiếp theo nếu Vx != Vy

Annn - LD I, addr

Lưu giá trị nnn vào thanh ghi I

Bnnn - JP V0, addr

Đưa program counter tới vị trí nnn + V0

Cxkk - RND Vx, byte

Gán giá trị Vx = random byte AND kk

Interpreter sẽ khởi tạo một số ngẫu nhiên (random) có giá trị từ 0 đến 255, sau đó AND với giá trị của kk. Kết quả lưu vào Vx

Các lệnh tương tác (display, keyboard, sound...)

Dxyn - DRW Vx, Vy, nibble

Vẽ ra màn hình - Đây là lệnh quan trọng nhất trong số tất cả các lệnh

Interpreter sẽ đọc n byte từ bộ nhớ, bắt đầu từ địa chỉ được lưu trong thanh ghi I. Các byte này sẽ được hiển thị dưới dạng một sprite trên màn hình từ toạ độ (Vx, Vy).

Sprite được vẽ ra màn hình theo phép XOR, nếu có pixel nào bị xoá vì phép toán này thì VF sẽ được gán là 1, ngược lại thì gán bằng 0.

Nếu các điểm của sprite nằm ở bên ngoại phạm vi hiển thị của màn hình thì sẽ được vẽ ra ngay tại các cạnh biên của màn hình gần với nó nhất.

Ex9E - SKP Vx

Skip lệnh tiếp theo nếu phím có giá trị của Vx được nhấn.

Kiểm tra bàn phím, nếu phím được nhấn có giá trị (key code) bằng vơi giá trị của Vx thì program counter sẽ được tăng lên 2.

ExA1 - SKNP Vx

Skip lệnh tiếp theo nếu phím có giá trị của Vx không được nhấn.

Tương tự như lệnh trên, nhưng lệnh này tăng PC lên 2 nếu phím có key code là Vx không được nhấn xuống.

Fx0A - LD Vx, K

Chờ bắt sự kiện nhấn phím, lưu key code vào Vx

Lệnh này sẽ ngừng chương trình cho tới khi có phím được nhấn.

Fx07 - LD Vx, DT

Gán Vx = delay timer value

Lưu giá trị của thanh ghi DT vào thanh ghi Vx

Fx15 - LD DT, Vx

Gán delay timer = Vx

Gán giá trị của thanh ghi DTVx để bắt đầu thực hiện việc chờ (delay), xem giải thích về Delay Timer ở bài trước.

Fx18 - LD ST, Vx

Gán sound timer = Vx

Gán giá trị của thanh ghi ST thành Vx để bắt đầu thực hiện phát âm thanh, xem giải thích về Sound Timer ở bài trước.

Fx1E - ADD I, Vx

Gán I = I + Vx

Lưu tổng số của IVx vào Vx

Fx29 - LD F, Vx

Gán I = vị trí của sprite kí tự Vx

Gán giá trị của thanh ghi I thành vị trí của kí tự hex font dựng sẵn tương ứng với Vx.

Fx33 - LD B, Vx

Interpreter lấy giá trị thập phân của Vx, lưu các số hàng trăm vào bộ nhớ ở vị trí I, các số hàng chục vào vị trí I + 1, các số hàng đơn vị ở vị trí I + 2

Fx55 - LD [I], Vx

Lưu giá trị từ thanh ghi V0 vào các thanh ghi Vx trong bộ nhớ, bắt đầu từ địa chỉ trong I. Sang phần sau sẽ rõ hơn trong quá trình implement.

Fx65 - LD Vx, [I]

Đọc giá trị từ các thanh ghi Vx, bắt đầu từ I vào V0


Trên đây là toàn bộ các opcode mà chúng ta sẽ implement ở phần sau. Sau khi implement tất cả các opcode này thì chúng ta có thể load một ROM game mẫu và chơi thử.

Có một số lệnh của Super CHIP-8 nhưng để bài viết đơn giản, mình sẽ không đề cập đến. Sau này nếu có thời gian thì chúng ta sẽ implement thêm sau.

Hẹn gặp lại các bạn ở phần 3.

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Huy Trần

109 bài viết.
1592 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
155 46
Tại sao phải viết blog kĩ thuật? Có rất nhiều bài viết trên mạng nói về vấn đề tại sao một lập trình viên nên thường xuyên viết các bài blog kĩ thu...
Huy Trần viết 3 năm trước
155 46
White
149 39
(Ảnh) Tiếp tục sêri (Link) lần này, chúng ta sẽ cùng tìm hiểu và mô phỏng lại một chức năng mà mọi người đang bắt đầu sử dụng hằng ngày, đó là chứ...
Huy Trần viết 2 năm trước
149 39
White
104 17
Phần 1: Tự truyện Tui và Toán đã từng là hai kẻ thù không đội trời chung trong suốt hơn mười lăm năm ròng rã. Ngay từ ánh nhìn đầu tiên đã ghét nh...
Huy Trần viết 2 năm trước
104 17
Bài viết liên quan
White
16 3
Nhắc đến game giả lập, chắc không ai lạ gì và ai cũng từng chơi qua (giả lập NES, Gameboy, PS1, PS2,...) Và chắc hẳn, cũng có không ít bạn nung nấ...
Huy Trần viết gần 3 năm trước
16 3
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
109 bài viết.
1592 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!