- RV64 DynaRec của Box64 đã tiến bộ từ mức chỉ chạy được các game Linux native đơn giản cách đây 1 năm, lên đến giai đoạn có thể chạy The Witcher 3 trên PC RISC-V
- Bước ngoặt của tiến triển này là việc có thể dùng card đồ họa AMD, giúp giảm bớt các ràng buộc về OpenGL, từ đó có thể thử nghiệm nhiều chương trình x86 hơn và sửa lỗi
- Backend RV64 có số lượng lệnh x86 được triển khai ít hơn backend ARM64, và lệnh AVX vẫn còn là bài toán lớn phía RISC-V
- RISC-V thiếu khả năng trích xuất/chèn dải bit và lệnh nguyên tử 16 byte, nên chi phí dịch trong giả lập x86 cao hơn AArch64 hay LoongArch64
- The Witcher 3 thực sự chạy được bằng box64, đạt tối đa 15fps trong game và chạy đủ tốc độ ở menu chính
Hành trình để The Witcher 3 chạy được trên RISC-V
- Cách đây 1 năm, RV64 DynaRec chỉ chạy được những game Linux native tương đối dễ chạy như Stardew Valley, World of Goo
- Khi đó có hai nút thắt lớn
- Trong quá trình nhanh chóng bổ sung lệnh x86_64 vào backend RISC-V, vẫn còn sót lại nhiều lỗi DynaRec
- GPU tích hợp IMG trên VisionFive 2 và LicheePi 4A chỉ hỗ trợ OpenGL ES chứ không hỗ trợ OpenGL
- Nhờ gl4es có thể có được một phần hỗ trợ OpenGL để chạy các game như Stardew Valley, nhưng vẫn chưa đủ cho các game Linux nặng hơn và các game Windows phổ biến
- Milk-V Pioneer của Sophgo là một PC RISC-V 64 lõi và cung cấp khe PCIe để gắn card đồ họa
- Một cộng tác viên khác là xctan cũng tìm ra cách “kết nối” card đồ họa AMD với VisionFive 2 thông qua giao diện M.2
- Khi đã có thể dùng card đồ họa AMD, phạm vi chương trình x86 có thể thử nghiệm được mở rộng, kéo theo hàng loạt đợt sửa lỗi RV64 DynaRec và bổ sung lệnh x86
- Kết quả là ngay từ lần đầu thử chạy, The Witcher 3 đã hoạt động
Trạng thái hiện tại của RV64 DynaRec
- Tập lệnh x86 rất lớn, và quy mô triển khai cũng khác nhau giữa các backend
- Backend ARM64 đã triển khai tổng cộng hơn 1.600 lệnh x86
- Backend RV64 đã triển khai khoảng 1.000 lệnh x86
- Trong số này có hơn 300 lệnh AVX mới được hỗ trợ, và phía RISC-V thì vẫn chưa được triển khai chút nào
- Ngay cả trong việc triển khai lệnh SSE, RISC-V cũng bất lợi về hiệu năng
- Backend RV64 triển khai lệnh SSE bằng lệnh vô hướng
- AArch64 dùng phần mở rộng Neon, còn LoongArch64 dùng phần mở rộng LSX
- Vì khác biệt này, hiệu năng thấp hơn đáng kể so với hai backend kia
- RISC-V có phần mở rộng vector là RVV
- Milk-V Pioneer hỗ trợ phần mở rộng xtheadvector, một biến thể của RVV 0.7.1
- SoC SpacemiT K1/M1 hỗ trợ RVV 1.0 đã được chuẩn hóa
- Banana Pi F3 và Milk-V Jupiter dùng SoC này hiện đã có thể mua được
- Gần đây box64 đã được bổ sung hỗ trợ RVV cơ bản cùng một vài triển khai cho các lệnh SSE phổ biến
- Công việc với RVV vẫn còn ở giai đoạn rất sớm, nên hiện chưa giúp cải thiện hiệu năng
Những lệnh RISC-V đặc biệt còn thiếu cho giả lập x86
- Xét từ góc độ giả lập x86, RISC-V là kiến trúc có mức độ biểu đạt thấp nhất trong 3 kiến trúc được hỗ trợ
- So với AArch64 và LoongArch64, nó thiếu các lệnh tiện dụng, nên cần nhiều lệnh hơn để giả lập cùng một hành vi
- Có hai khả năng đặc biệt quan trọng bị thiếu
- Chọn một dải bit cụ thể từ một thanh ghi và đưa sang thanh ghi khác
- Chèn một phần bit của một thanh ghi vào một phạm vi cụ thể trong thanh ghi khác
- LoongArch64 và AArch64 đều có các lệnh tương ứng
- LoongArch64 dùng
BSTRPICK.DvàBSTRINS.D - ARM64 dùng opcode
UBFXvàBFI
- LoongArch64 dùng
- RISC-V không có lệnh tương ứng như vậy, cả trong phần mở rộng chính thức lẫn phần mở rộng của nhà cung cấp
Ví dụ ADD AH, BL cho thấy chi phí dịch
- ISA x86 có xu hướng giữ nguyên các bit không bị thay đổi, nên việc thao tác trên thanh ghi bộ phận là rất quan trọng
- Với
ADD AH, BL, box64 phải thực hiện các bước sau- Trích xuất byte thấp nhất của
RBX - Cộng vào byte thấp thứ hai của
RAX - Chèn lại kết quả vào byte thấp thứ hai của
RAX - Giữ nguyên các byte còn lại của
RAX
- Trích xuất byte thấp nhất của
- Trên LoongArch64, có thể triển khai đơn giản và trực quan bằng
BSTRPICK.D,ADD,BSTRINS.D - Trên RISC-V, để làm điều tương tự phải kết hợp dịch bit, mask,
AND,OR... nên cần tới 10 lệnh - Đây không phải trường hợp cá biệt, vì trong x86 có nhiều lệnh có dạng tương tự, khiến việc triển khai trên RISC-V trở nên rườm rà hơn
Hạn chế của lệnh nguyên tử 16 byte
- x86 có tiền tố LOCK để thực hiện các phép toán nguyên tử lock-free
- box64 chủ yếu giả lập điều này bằng chuỗi LR/SC
- LR/SC là viết tắt của Load-Reserved / Store-Conditionally
- Ví dụ
LOCK ADD [RAX], RCXsẽ được sinh thành dạngLR.D,ADD,SC.D, rồi nhánh có điều kiện
- Nếu địa chỉ
RAXkhông được căn chỉnh thì sẽ phức tạp hơn, nhưng nhìn chung cách này hoạt động tốt - Vấn đề nằm ở
LOCK CMPXCHG16B- Lệnh này so sánh
RDX:RAXvới 16 byte trong bộ nhớ - Tùy theo điều kiện, nó hoán đổi
RCX:RBXvào địa chỉ bộ nhớ đó
- Lệnh này so sánh
- AArch64 và LoongArch64 có một số lệnh nguyên tử 16 byte có thể dùng để triển khai
- RISC-V không có lệnh tương ứng, nên không thể triển khai đầy đủ như các kiến trúc kia
- Nhiều chương trình, bao gồm cả game Unity, sử dụng
LOCK CMPXCHG16B
Kết quả chạy thực tế
- Dù vẫn còn các ràng buộc, The Witcher 3 vẫn chạy được trên RISC-V bằng box64
- Hiệu năng trong game đạt tối đa 15fps
- Ở menu chính, game chạy đủ tốc độ
- Với một cỗ máy vốn không được thiết kế để chạy game AAA, đây không phải là kết quả tệ
1 bình luận
Ý kiến trên Hacker News
Từ góc độ một người không làm việc bên mảng chip, tôi tò mò không biết khi làm phần mềm nhắm tới RISC-V thì kỹ sư phần mềm cần làm gì khác đi
Tôi tự hỏi liệu kích thước tệp thực thi có lớn hơn nên phải tối ưu tính cục bộ của cache một cách quyết liệt hơn không, và cũng thắc mắc liệu có loại phần mềm nào như game hay web server phù hợp với CISC hoặc RISC hơn không
Về cơ bản thì không có nhiều thứ phải thay đổi trong cách tiếp cận phần mềm; khác biệt lớn nhất so với x86-64 là có 32 thanh ghi nên có thể giữ được nhiều giá trị trung gian hơn trước khi bị đẩy ra stack, nhưng ARM cũng có 32 thanh ghi nên khá tương tự. Thường thì nếu không làm tối ưu vi mô, bạn sẽ không phải bận tâm nhiều
Đi sâu hơn thì mở rộng vector (V/RVV) không có trong ISA rv64gc cơ bản, nên tùy mục tiêu mà có thể không nhận được tối ưu SIMD; popcount và đếm số bit 0 ở đầu/cuối cũng không có trong rv64gc cơ bản mà cần Zbb. Ngoài ra, lựa chọn không nhánh như
a ? b : ctrong rv64gc cơ bản cần 4~5 lệnh, với Zicond thì cần 3 lệnh, trong khi trên x86-64 và aarch64 chỉ cần 1 lệnhCác profile của RISC-V giải quyết phần nào hai vấn đề trên. Ví dụ Android yêu cầu rva23, trong đó bắt buộc có RVV, Zbb, Zicond v.v. Tuy nhiên nếu bản phân phối Linux nhắm tới rva20/rv64gc thì với mã biên dịch sẵn không có dynamic dispatch, trên thực tế sẽ rất lâu mới dùng được các mở rộng đó. x86-64 cũng có vấn đề tương tự, nhưng ARM thì ít mở rộng hơn nhiều nên đỡ hơn; ngoại lệ lớn nhất là SVE, dù hiện vẫn chưa được hỗ trợ rộng rãi
Khác biệt lớn nhất là mô hình bộ nhớ yếu, nhưng đó cũng là đặc tính có ở hầu hết kiến trúc không phải x86 như ARM, và ngay từ đầu mã đã không nên phụ thuộc vào mô hình bộ nhớ mạnh
Mật độ mã thực thi của x86 không tốt lắm vì lý do lịch sử, nên kích thước tệp thực thi sẽ không tăng nhiều như bạn nghĩ. RISC-V có mở rộng lệnh nén và ARM 32-bit có mở rộng Thumb đều khá cô đọng
Điều quan trọng không phải là CISC hay RISC mà là sự hiện diện và chất lượng của lệnh vector cùng các mở rộng mã hóa. Mã hóa/giải mã video phụ thuộc rất nhiều vào lệnh vector để đạt hiệu năng tốt, còn mã hóa toàn bộ đĩa hay hashing có thể hưởng lợi từ các lệnh chuyên dụng tăng tốc những thuật toán cụ thể như AES, SHA256
Điểm cốt lõi là thoát khỏi ràng buộc bằng sáng chế ARM, và theo hướng khởi đầu mới dựa trên những bài học đã rút ra
Điều này làm tôi nhớ đến một người Nga nổi tiếng từng chạy Atomic Heart trên Elbrus 8S
Elbrus có trình biên dịch dịch mã native, và theo tôi biết thì nó khá ổn. Atomic Heart chạy ở khoảng 15~25fps nên cũng tương đối chơi được
Bài viết này hơi thiếu phần giải thích “cơ bản”. Tôi đã nghĩ là họ chạy bằng kiểu Wine port nào đó, nhưng thực ra có vẻ như họ đang hiện thực ISA x86_64 theo cách nào đó trên chip RISC-V
Sẽ rất hay nếu có ai giải thích thêm phần đó hoạt động như thế nào
Đây là trình giả lập, nhưng một số thư viện “hệ thống” như libc, libm, SDL, OpenGL thì dùng bản native, nên khá dễ tích hợp với hầu hết ứng dụng và trong một số trường hợp hiệu năng còn có thể cao đến mức đáng ngạc nhiên. Wine cũng có thể được biên dịch native để chạy
Kết quả thật đáng kinh ngạc. Khối lượng công việc khổng lồ, và trong một số trường hợp có vẻ như đang chạm tới giới hạn của RISC-V
Có lẽ các lệnh gather/scatter bit nên được đưa vào như một mở rộng
Điều thú vị là trong 3 kiến trúc được hỗ trợ trong bối cảnh giả lập x86, RISC-V là kiến trúc có khả năng biểu đạt thấp nhất.
Trong các lớp lịch sử khoa học máy tính, tôi đã học rằng RISC là máy tính với tập lệnh rút gọn, nhưng khi nhìn vào các đề xuất profile hay bài viết về RISC-V dạo này, thường thấy kiểu lập luận rằng “chỉ cần thêm vài lệnh nữa để đạt tương đương chức năng”. Tôi hiểu rằng với nhiều người, RISC-V là một lựa chọn thay thế tiện lợi cho các nền tảng khác, nhưng tôi cũng tự hỏi liệu điều này có nghĩa là giấc mơ của RISC đã chết hay chưa.
Theo ký ức của tôi khi từng đọc đặc tả RISC-V, họ khá nghiêm khắc trong việc không thêm các lệnh “combo”, vì các chuỗi lệnh phổ biến có thể được hợp nhất ở frontend.
Điểm thiếu hụt của RISC-V so với x86/ARM có vẻ không hẳn do chủ nghĩa nguyên giáo RISC, mà do đặc tả khởi đầu từ các chip nhúng rất cơ bản rồi theo thời gian mới bổ sung các mở rộng cho CPU ứng dụng. Ngay cả RV32I cơ bản cũng không có phép nhân số nguyên. Đáng tiếc là đã mất quá nhiều thời gian để chốt các tranh luận về thao tác bit và các mở rộng SIMD/vector, và kết quả là xuất hiện những khoảng trống tính năng đang được nói đến hiện nay.
Tuy vậy, cái giá là một số lệnh tiện lợi cho hiệu năng cao sẽ bị loại bỏ.
Pipeline đơn giản cũng có ưu điểm là tiêu tốn ít nguồn lực kỹ thuật hơn đối với các nhóm làm thiết kế hiệu năng cao, nhờ đó họ có thể dành nhiều thời gian hơn cho tối ưu hóa.
RISC nhìn chung là một triết lý đơn giản hóa, nhưng mức độ thì rất đa dạng. MIPS được đơn giản hóa gần như RISC-V, còn ARM và POWER mang tính thỏa hiệp hơn, và dường như không gặp vấn đề lớn khi đối đầu với x86 trong phân khúc hiệu năng cao.
Thị trường bộ xử lý còn có nhiều ngách khác ngoài chạy ứng dụng, như nhúng, tăng tốc, v.v. Ở ngách cụ thể là application core thì tôi hơi bi quan về RISC-V, nhưng nhìn rộng hơn thì nó có tiềm năng lớn, thậm chí có thể thống trị một vài ngách thương mại, và là công cụ tuyệt vời cho giáo dục lẫn nghiên cứu.
Đặc trưng của RISC cổ điển là hầu hết các lệnh thao tác dữ liệu chỉ hoạt động trên thanh ghi, còn lệnh bộ nhớ về cơ bản chỉ là load/store với thanh ghi, nên cần rất nhiều thanh ghi. Stack cũng phải tự dựng, vì để truyền tham số phải thao tác stack trực tiếp, và thay vì có lệnh CALL/JSR thì người ta dùng các lệnh cơ bản để load/store trực tiếp vào thanh ghi con trỏ lệnh. Mã hóa lệnh có tính dự đoán cao và mọi lệnh đều có cùng kích thước. Nhiều kiến trúc RISC còn có một thanh ghi luôn đọc ra giá trị 0 và không thể ghi, dùng để đặt giá trị về 0.
Cách tiếp cận này đã hiệu quả, nhưng sau đó thực thi ngoài thứ tự và SIMD đã làm nó bớt quan trọng. Dòng lệnh thô gần giống một bản khai báo con đường dẫn tới kết quả mong muốn, chứ không có nghĩa CPU thực sự chạy đúng nguyên xi như vậy. Phía sau còn có thực thi suy đoán, dự đoán nhánh và đổi tên thanh ghi. SIMD thì gần hơn với một không gian thanh ghi rộng và các lệnh hoạt động trên mọi giá trị bên trong đó. Cuối cùng, thực thi ngoài thứ tự và SIMD đã nắm quyền chủ đạo.
Về mặt lý thuyết, nếu biên dịch mã nguồn gốc cho RISC thì sẽ cho ra một binary hoàn toàn khác, và có thể sẽ không cần những lệnh cụ thể đó.
Nhưng thực tế thì có lẽ chẳng ai sẽ biên dịch các trò chơi này thật sự cho RISC-V.
Ảnh chụp màn hình cho thấy RAM là 31GB, rõ ràng lớn hơn mức cấu hình tối đa của bo mạch phát triển được nhắc đến. Ở đây họ đang dùng thứ khác à?
Nếu là bây giờ thì tốt hơn nên dùng một trong các lựa chọn mới hơn với nhiều nhân nhanh hơn, có triển khai RVA22 và RVV 1.0.
Đây có phải là 86Box không? Thật vui khi nó gợi lại thời tôi mua Amstrad PC1512.
Khi thêm 2 card ổ cứng 500MB và mở rộng bộ nhớ 128KB để lên 640KB thì mọi thứ vui hơn hẳn. Ban đầu chỉ có 2 ổ mềm 360KB, rồi vài năm sau mới thêm một card ổ cứng 32MB. Tôi cũng có Borland TurboPascal và Zortech C. Đúng là những ngày tháng tuyệt vời.
Dù vậy, tôi cũng nhớ thời dùng Amstrad PC1512.
Tôi tự hỏi liệu một ngày nào đó sẽ có một hệ thống kết hợp vài CPU RISC-V lớn với một “GPU” được triển khai bằng cả một cụm CPU RISC-V nhỏ hay không.
Có lẽ nó sẽ đi kèm các tính năng vector phù hợp, và như một câu hỏi bên lề, tôi cũng tò mò liệu kiểu vector cổ điển thay cho packed SIMD có thể hữu ích trên GPU hay không.
Một thành tựu ấn tượng khác về mặt kỹ thuật của Witcher 3 là bản port lên Switch, và nó chạy thực sự tốt.
Điều đó cho thấy có thể làm được bao nhiêu thứ nhờ tối ưu hóa, đồng thời cho thấy bao nhiêu tài nguyên bị lãng phí trên PC vì tối ưu hóa tệ.
Đây không phải là so sánh tương đương, và phạm vi những gì được hiển thị trên màn hình cũng rất khác, nên khó có thể khẳng định đó là tối ưu hóa tệ trên PC.
Sẽ rất tốt nếu kiểu phản hồi ở cấp ISA này được chuyển tới những người phía RVI
Hôm qua tôi kiểm tra lại [1], ví dụ trong bài thực ra đã có thể làm bằng 4 lệnh RISC-V. Chỉ là hơi khó nghĩ ra
# a0 = rax, a1 = rbxslli t0, a1, 64-8rori a0, a0, 16add a0, a0, t0rori a0, a0, 64-16[1] https://www.reddit.com/r/RISCV/comments/1f1mnxf/box64_and_ri...
Thực ra việc thiếu trích xuất bitfield là một sai sót quá rõ ràng, nên đây là ví dụ tôi thích nhất để cho thấy ISA RISC-V phi lý đến mức nào. Ví dụ thứ hai là việc nó không có chế độ địa chỉ hóa tử tế
Một số thiết kế RISC-V tốt hơn thực sự đã triển khai lệnh tùy chỉnh cho việc này. Ví dụ có BEXTM của Hazard3: https://github.com/Wren6991/Hazard3/blob/stable/doc/hazard3....