Bài tập lập trình hợp ngữ mips có lời giải năm 2024

Đặt vấn đề: Đánh giá tình trạng tủy xương đóng vai trò quan trọng trong phân chia giai đoạn u lympho, giúp tiên lượng cũng như lựa chọn phương pháp điều trị. Mục tiêu: Khảo sát tỉ lệ và đặc điểm mô học tủy xương các trường hợp u lympho tế bào B xâm nhập tủy xương. Đối tượng và phương pháp: Nghiên cứu mô tả cắt ngang các trường hợp u lympho tế bào B tại Bệnh viện Truyền máu Huyết học từ tháng 01/2019 đến tháng 12/2020. Kết quả: Tỉ lệ u lympho tế bào B xâm nhập tủy xương là 64,8%. 40% trường hợp u lympho tế bào B độ ác cao và 80% trường hợp u lympho tế bào B độ ác thấp xâm nhập tủy xương. LPL và BL có tỉ lệ xâm nhập tủy xương cao nhất (100%), thấp nhất là DLBCL (25,6%). Hình thái xâm nhập thường gặp nhất là dạng lan tỏa (53%), tiếp theo là dạng hỗn hợp (31%). Dạng cạnh bè xương ghi nhận trong 50% FL và 10% DLBCL (FL chuyển dạng DLBCL). Dạng trong mạch máu trong xoang 0%. 72,2% MZL xâm nhập kiểu hỗn hợp. Kết luận: U lympho tế bào B độ ác thấp có tỉ lệ xâm nhập tủy xương cao hơn u lymph...

Việc khảo sát, đánh giá về kiểu hình cũng như kiểu gen là cần thiết nhằm làm tăng hiệu quả cho quá trình nhận dạng, phát triển và chọn tạo giống mới đối với cây trồng. Nguồn gen thuộc một số dòng bơ đã qua chọn lọc để canh tác được thu thập từ một số nơi trong địa bàn tỉnh Lâm Đồng để phân tích đa dạng di truyền và nhận dạng giống. Đặc điểm sơ bộ về hình thái quả và năng suất của 11 dòng bơ tiềm năng đã được ghi nhận để hỗ trợ cho cơ sở dữ liệu nhận dạng dòng. Với đặc trưng nhận dạng DNA thu nhận được với 10 mồi ISSR, chúng tôi thu được tổng số 125 band điện di trên gel để tiến hành phân tích đa dạng di truyền tập hợp 11 mẫu khảo sát đại diện cho 11 dòng trên, kết quả cho thấy: tập hợp mẫu có mức dị hợp trông đợi (chỉ số đa dạng gene) đạt He = h = 0,3072, chỉ số Shannon đạt: I = 0,4608, tỷ lệ band đa hình: PPB = 91,84%. Cũng sử dụng 10 mồi ISSR như trên, từ đặc trưng nhận dạng DNA của 18 mẫu đại diện cho 6 dòng bơ tiềm năng (mỗi dòng 3 mẫu), dựa trên sự xuất hiện hay thiếu vắng các ...

Tóm tắt: Nghiên cứu này đánh giá thực trạng hợp tác, liên kết của nông hộ trong sản xuất và tiêu thụ lúa hữu cơ tại xã Thủy Phù, thị xã Hương Thủy, Thừa Thiên Huế. Số liệu sơ cấp được thu thập thông qua khảo sát 60 hộ sản xuất lúa hữu cơ bằng bảng hỏi bán cấu trúc. Kết quả nghiên cứu cho thấy rằng khoảng 80% lúa hữu cơ được tiêu thụ thông qua hợp đồng, tỉ lệ hộ thực hiện theo hợp đồng chiếm tỉ lệ 98%. Liên kết giữa nông hộ sản xuất lúa hữu cơ là liên kết miệng, chưa chặt chẽ; liên kết này thực hiện chủ yếu thông qua trao đổi thông tin về kỹ thuật sản xuất; việc trao đổi thông tin về đầu vào và đầu ra chưa được nông hộ quan tâm. Liên kết giữa hộ với doanh nghiệp được thực hiện qua qua hợp đồng và khá chặt chẽ. Ngoại trừ điều khoản về xử lý rủi ro, các điều khoản về giá cả, phương thức thanh toán, số lượng và chất lượng sản phẩm, và phương thức giao nhận được đánh giá khá chặt chẽ trong hợp đồng. Các dịch vụ đầu vào, đầu ra và giá cả sản phẩm trong liên kết sản xuất và tiêu thụ lúa hữ...

Tóm tắt: Lòng trung thành của du khách đối với điểm đến là một yếu tố quan trọng nhằm góp phần thúc đẩy sự phát triển của một điểm đến du lịch. Nghiên cứu này được thực hiện dựa trên cơ sở điều tra khảo sát ý kiến của 231 du khách trong nước và quốc tế đến Hội An. Mục tiêu chính của nghiên cứu nhằm kiểm tra mối quan hệ giữa động cơ đẩy và kéo, sự hài lòng và lòng trung thành của du khách đối với điểm đến Hội An. Kết quả nghiên cứu cho thấy nhân tố đẩy, nhân tố kéo, cùng với sự hài lòng có ảnh hưởng đến lòng trung thành của du khách đối với điểm đến Hội An. Ngoài ra, trong bối cảnh du lịch di sản, du khách có xu hướng trung thành với điểm đến với nhu cầu được tìm hiểu lịch sử, đến thăm những điểm tham quan di sản – văn hóa, gặp gỡ những con người mới và giao lưu với cộng đồng địa phương. Theo đó, tác giả đề xuất một số giải pháp nhằm nâng cao lòng trung thành của du khách, góp phần thu hút du khách quay trở lại điểm đến di sản Hội An, bao gồm định vị và phát triển hình ảnh điểm đến H...

Hợp ngữ (Assembly language) là ngôn ngữ có khả năng chuyển đổi 1-1 sang ngôn ngữ máy. Bài viết này sẽ trình bày hợp ngữ dành cho các dòng máy có kiến trúc MIPS.

Để lập trình và chạy hợp ngữ MIPS, có thể dùng Mars: courses.missouristate.edu/KenVollmar/mars/

CISC và RISC

Hợp ngữ và kiến trúc máy tính được chi làm 2 loại: CICS và RISC. Đại diện tiêu biểu cho CISC là x86 - được sử dụng trên các máy tính cá nhân và server. Đại diện cho RISC là ARM và MIPS. ARM được sử dụng trong các thiết bị di động và MIPS được sử dụng trong một số siêu máy tính, và các thiết bị như router, Nintendo 64, Sony Playstation 2.

Khác biệt giữa CISC và RISC:

CISC là Complex insrtuction set computer và RICS là Reduced instruction set computer. Hợp ngữ của CISC rất phức tạp và ngược lại RISC thì đơn giản hơn, vì vậy các máy CISC tiêu tốn điện năng nhiều hơn các máy RISC.

Các bạn có thể đọc thêm về so sánh CISC và RISC ở đây: cs.stanford.edu/people/eroberts/courses/soco/projects/risc/risccisc/

Thanh ghi

MIPS có tổng cộng 32 thanh ghi (register) để lưu giá trị, được đánh số từ 0 đến 31. Để truy cập và thao tác trên một thanh ghi, ta dùng cú pháp

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

0 + số thứ tự thanh ghi. Ví dụ:

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

1,

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

2,

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

3,…

Ngoài ra, MIPS có quy ước mục đích sử dụng của mỗi thanh ghi, khi lập trình nên tuân thủ các quy ước này. Vì thế, người ta thường truy cập thanh ghi thông qua tên của chúng:

Tên Thanh ghi Ý nghĩa $zero 0 Thanh ghi này luôn chứa giá trị 0 $at 1 Assembler Temporary - Được dành riêng cho các mục đích khác, khi viết hạn chế dùng thanh ghi này $v0, $v1 2, 3 Lưu giá trị trả về của hàm $a0 - $a3 4 - 7 Lưu tham số truyền vào của hàm $t0 - $t7 8 - 15 Lưu biến tạm $s0 - $s7 16 - 23 Lưu biến $t8, $t9 24, 25 Như các $t ở trên $k0, $k1 26, 27 Được dùng cho nhân HĐH sử dụng $gp 28 Pointer to global area $sp 29 Stack pointer $fp 30 Frame pointer $ra 31 Return address, sử dụng cho việc gọi hàm

MIPS có tư tưởng register-to-register - load/store, nghĩa là các lệnh đều thao tác trên thanh ghi. Khi cần sử dụng bộ nhớ, ta sẽ có các lệnh riêng để nạp dữ liệu từ bộ nhớ vào thanh ghi.

Mỗi thanh ghi lưu trữ một giá trị 32-bit. Không như khái niệm biến ngôn ngữ lập trình cấp cao, thanh ghi trong hợp ngữ không có kiểu dữ liệu, cách ta sử dụng thanh ghi sẽ quyết định kiểu dữ liệu là gì.

Các cấu trúc lệnh của MIPS

Phần này trình bày cấu trúc của các lệnh hợp ngữ khi được dịch sang ngôn ngữ máy. Mỗi lệnh trong MIPS đều có độ dài là 32 bit.

Có thể xem mỗi lệnh như một hàm trong ngôn ngữ lập trình. Vì vậy, ta cần có tên lệnh, các tham số truyền vào và kiểu của các tham số truyền vào - trong trường hợp này là kích thước của mỗi tham số truyền vào (vì không có khái niệm kiểu dữ liệu trong hợp ngữ).

MIPS chú trọng tính đơn giản của tập lệnh, vì vậy chỉ có 3 kiểu lệnh chính: R-format, I-format, J-format.

R-format

R-format có 6 tham số:

Tên tham số op rs rt rd shamt funct Độ dài (bit) 6 5 5 5 5 6

Giải thích:

  • op: opcode, trường này sẽ cho máy biết lệnh này là lệnh nào. Trong trường hợp R-format thì các lệnh đều dùng chung opcode là 0.
  • rs, rt: source register và destination register, 2 thanh ghi cần thực hiện tính toán.
  • rd: register destination, thanh ghi lưu kết quả của lệnh.
  • shamt: shift amount, số bit cần dịch trong lệnh dịch trái và dịch phải.
  • funct: Vì các lệnh R-format đều có chung opcode bằng 0 nên ta thêm trường này để cho máy biết cần thực hiện lệnh nào.

I-format

Lệnh I-format dùng cho thao tác giữa thanh ghi và một hằng số được lưu sẵn trong lệnh. Cấu trúc như sau:

Tên tham số op rs rt immediate Độ dài (bit) 6 5 5 16

Giải thích:

  • op: opcode, cho máy biết đây là lệnh gì. Vì I-format không có trường

    add $s0, $s1, $s2 # $s0 = $s1 + $s2 sub $s0, $s1, $s2 # $s0 = $s1 - $s2 addi $s0, $s0, 123 # $s0 = $s0 + 123 addi $s0, $s2, -123 # $s0 = $s2 - 123

    4 nên các lệnh I-format không dùng chung opcode như các lệnh R-format.
  • rs, rt: source register và target register.
  • immediate: Một giá trị hằng số mà lệnh sử dụng.

J-format

J-format dành cho các lệnh nhảy (

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

5 trong C), có cấu trúc:

Tên tham số op target address Độ dài (bit) 6 26

Giải thích:

  • op: opcode, cho máy biết đây là lệnh gì.
  • target address: Địa chỉ rút gọn của lệnh cần nhảy đến, địa chỉ gốc có 32 bit, ta rút gọn 6 bit như sau:
    • Xóa 2 bit thấp nhất của địa chỉ. Vì địa chỉ của các lệnh trong MIPS luôn chia hết cho 4 nên 2 bit thấp nhất luôn bằng 0.
    • 4 bit cao nhất xem như bằng với 4 bit cao nhất của lệnh hiện tại.

Các lệnh tính toán

Lệnh cộng và trừ

4 lệnh

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

6,

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

7,

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

8,

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

9 dùng để cộng/trừ giá trị của 2 thanh ghi, và lưu kết quả vào thanh ghi đích. Cú pháp:

<tên lệnh> <thanh ghi đích>, <thanh ghi 1>, <thanh ghi 2>

2 lệnh

not A = A nor 0
A xor B = (A or B) and (not A or not B) = (A and not B) or (not A and B)

0,

not A = A nor 0
A xor B = (A or B) and (not A or not B) = (A and not B) or (not A and B)

1 dùng để cộng một thanh ghi với 1 hằng số, rồi lưu vào thanh ghi đích. Cú pháp:

<tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

Ví dụ:

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

Khác biệt giữa

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

8 và

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

6:

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

6 sẽ báo lỗi khi có tràn số, còn

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

8 thì không. Tương tự với các lệnh có

not A = A nor 0
A xor B = (A or B) and (not A or not B) = (A and not B) or (not A and B)

6 và không có

not A = A nor 0
A xor B = (A or B) and (not A or not B) = (A and not B) or (not A and B)

6 khác.

Các lệnh tính toán logic

Có 3 lệnh:

not A = A nor 0
A xor B = (A or B) and (not A or not B) = (A and not B) or (not A and B)

8,

not A = A nor 0
A xor B = (A or B) and (not A or not B) = (A and not B) or (not A and B)

9,

lui $t0, 0x1234
ori $t0, $t0, 0x5678
add $s0, $s0, $t0

0. NOR là thao tác “NOT OR”:

lui $t0, 0x1234
ori $t0, $t0, 0x5678
add $s0, $s0, $t0

1. Cú pháp của 3 lệnh này tương tự như lệnh

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

6 ở trên.

Tương tự, ta cũng có lệnh

lui $t0, 0x1234
ori $t0, $t0, 0x5678
add $s0, $s0, $t0

3 và

lui $t0, 0x1234
ori $t0, $t0, 0x5678
add $s0, $s0, $t0

4 để tính AND/OR của một thanh ghi với một hằng số.

Các phép toán logic khác có thể được tính từ 3 phép trên:

not A = A nor 0
A xor B = (A or B) and (not A or not B) = (A and not B) or (not A and B)

Tính toán với các hằng số 32 bit

Dễ thấy các lệnh thao tác với hằng số ở trên đều có giới hạn 16 bit cho hằng số. Để giải quyết vấn đề này, MIPS cung cấp lệnh

lui $t0, 0x1234
ori $t0, $t0, 0x5678
add $s0, $s0, $t0

5 (load upper immediate) với chức năng ghi một hằng số 16-bit vào 2 byte cao của thanh ghi, 2 byte thấp sẽ được gán bằng 0.

Ví dụ, ta cần cộng

lui $t0, 0x1234
ori $t0, $t0, 0x5678
add $s0, $s0, $t0

6 cho giá trị

lui $t0, 0x1234
ori $t0, $t0, 0x5678
add $s0, $s0, $t0

7:

lui $t0, 0x1234
ori $t0, $t0, 0x5678
add $s0, $s0, $t0

Lệnh dịch

2 lệnh

lui $t0, 0x1234
ori $t0, $t0, 0x5678
add $s0, $s0, $t0

8 và

lui $t0, 0x1234
ori $t0, $t0, 0x5678
add $s0, $s0, $t0

9 dùng để dịch trái và dịch phải. Đây là dịch logic, các giá trị trống sau khi dịch luôn là 0.

Cú pháp tương tự như

not A = A nor 0
A xor B = (A or B) and (not A or not B) = (A and not B) or (not A and B)

0 ở trên, tuy nhiên số bit cần dịch luôn là một số không âm từ 0 đến 31.

Các lệnh thao tác bộ nhớ

Mô hình bộ nhớ của MIPS

Khi cần tính toán với các giá trị được lưu trên RAM, ta phải nạp giá trị lên thanh ghi trước khi tính, sau đó lưu lại kết quả vào RAM (nếu cần).

Đơn vị nhớ nhỏ nhất mà MIPS có thể xử lý là byte (8 bit). MIPS cung cấp các lệng load/store với các kích thước 1, 2 và 4 byte. Tuy nhiên có quy tắc Alignment Restriction sau: “Địa chỉ vùng nhớ cần truy cập phải chia hết cho kích thước cần truy cập”. Ví dụ, đọc 4 byte bắt đầu từ ô nhớ có địa chỉ 10 là không hợp lệ.

Ngoài ra, MIPS lưu trữ dữ liệu theo dạng Big Endian, tức là byte cao sẽ được lưu ở địa chỉ thấp. Ví dụ, số 12345678h (thập lục phân) khi được lưu trong bộ nhớ thì byte đầu tiên sẽ là 12h, byte tiếp theo là 34,…

Lệnh load/store

Cú pháp:

Trong đó:

  • r1: thanh ghi cần nạp dữ liệu vào / lấy dữ liệu ra.
  • r2: thanh ghi lưu địa chỉ gốc.
  • offset: hằng số nguyên (16 bit), giá trị này sẽ được cộng với giá trị của r2 để được địa chỉ cần nạp vào / lấy ra.

Tên các lệnh:

  • lw $s1, 0($s0) # $s1 = *x lw $s1, 4($s0) # $s1 = x[1] sw $s1, 8($s0) # x[2] = $s1

    1 (load word),

    lw $s1, 0($s0) # $s1 = *x lw $s1, 4($s0) # $s1 = x[1] sw $s1, 8($s0) # x[2] = $s1

    2 (load halfword),

    lw $s1, 0($s0) # $s1 = *x lw $s1, 4($s0) # $s1 = x[1] sw $s1, 8($s0) # x[2] = $s1

    3 (load byte): Đọc 4/2/1 byte. Đối với

    lw $s1, 0($s0) # $s1 = *x lw $s1, 4($s0) # $s1 = x[1] sw $s1, 8($s0) # x[2] = $s1

    2 và

    lw $s1, 0($s0) # $s1 = *x lw $s1, 4($s0) # $s1 = x[1] sw $s1, 8($s0) # x[2] = $s1

    3, vì thanh ghi có độ dài 4 byte, nhiều hơn lượng dữ liệu đọc được nên các bit trống sẽ được gán bằng bit dấu của số đọc được.
  • lw $s1, 0($s0) # $s1 = *x lw $s1, 4($s0) # $s1 = x[1] sw $s1, 8($s0) # x[2] = $s1

    6 (load halfword unsigned),

    lw $s1, 0($s0) # $s1 = *x lw $s1, 4($s0) # $s1 = x[1] sw $s1, 8($s0) # x[2] = $s1

    7 (load byte unsigned): tương tự như trên, tuy nhiên các bit trống được gán bằng 0.
  • lw $s1, 0($s0) # $s1 = *x lw $s1, 4($s0) # $s1 = x[1] sw $s1, 8($s0) # x[2] = $s1

    8 (store word),

    lw $s1, 0($s0) # $s1 = *x lw $s1, 4($s0) # $s1 = x[1] sw $s1, 8($s0) # x[2] = $s1

    9 (store halfword),

    j <đỉa chỉ cần nhảy tới hoặc nhãn>

    0 (store byte): lưu 4/2/1 byte dữ liệu trong thanh ghi vào bộ nhớ.

Ví dụ, ta có một mảng

j <đỉa chỉ cần nhảy tới hoặc nhãn>

1 được lưu trong

lui $t0, 0x1234
ori $t0, $t0, 0x5678
add $s0, $s0, $t0

6:

lw $s1, 0($s0) # $s1 = *x
lw $s1, 4($s0) # $s1 = x[1]
sw $s1, 8($s0) # x[2] = $s1

Một số lưu ý:

  • Các lệnh trên đều phải tuân theo quy tắc Alignment Restriction ở trên.
  • Đối với

    lw $s1, 0($s0) # $s1 = *x lw $s1, 4($s0) # $s1 = x[1] sw $s1, 8($s0) # x[2] = $s1

    9 và

    j <đỉa chỉ cần nhảy tới hoặc nhãn>

    0 sẽ lưu các byte thấp trong thanh ghi vào bộ nhớ.
  • Dữ liệu trong thanh ghi và bộ nhớ đều tuân theo quy tắc Big Endian.

Các lệnh điều khiển

Khi chương trình được thực thi, máy sẽ nạp chương trình lên bộ nhớ, đồng thời có một thanh ghi dành riêng để lưu địa chỉ của lệnh đang được thực thi, đây gọi là thanh ghi PC (program counter). Mỗi lần thực hiện xong một lệnh, mặc định PC sẽ được tự động tăng lên để chuyển sang lệnh tiếp theo.

Công việc của các lệnh điều khiển như nhảy, rẽ nhánh là gán lại địa chỉ của thanh ghi PC, để chương trình chuyển sang một đoạn khác.

Lệnh nhảy

Lệnh nhảy tương tự như

add $s0, $s1, $s2 # $s0 = $s1 + $s2
sub $s0, $s1, $s2 # $s0 = $s1 - $s2 
addi $s0, $s0, 123 # $s0 = $s0 + 123
addi $s0, $s2, -123 # $s0 = $s2 - 123

5 trong C, có 2 lệnh nhảy là

j <đỉa chỉ cần nhảy tới hoặc nhãn>

6 và

j <đỉa chỉ cần nhảy tới hoặc nhãn>

7, ngoài ra còn có

j <đỉa chỉ cần nhảy tới hoặc nhãn>

8 nhưng ta sẽ tìm hiểu lệnh này sau.

Cú pháp lệnh

j <đỉa chỉ cần nhảy tới hoặc nhãn>

6:

j <đỉa chỉ cần nhảy tới hoặc nhãn>

Thông thường, khi viết hợp ngữ ta chỉ cần dùng nhãn, trình dịch hợp ngữ sẽ tự chuyển đổi sang địa chỉ, ví dụ:

loop:
    addi $s0, $s0, 1
    j loop

Đoạn chương trình tên là một vòng lặp vô hạn.

j <đỉa chỉ cần nhảy tới hoặc nhãn>

7 cũng tương tự như

j <đỉa chỉ cần nhảy tới hoặc nhãn>

6, tuy nhiên ta đọc địa chỉ lệnh cần nhảy đến trong một thanh ghi. Ví dụ:

Cách hoạt động của lệnh nhảy:

  • Lệnh

    j <đỉa chỉ cần nhảy tới hoặc nhãn>

    7 sẽ gán PC bằng với thanh ghi được chỉ định
  • Ở lệnh

    j <đỉa chỉ cần nhảy tới hoặc nhãn>

    6 vì, tham số truyền vào chỉ có 26 bit, mà PC lại có đến 32 bit nên ta tính lại PC như sau:

    loop:
    addi $s0, $s0, 1  
    j loop  
    

    4, với

    loop:
    addi $s0, $s0, 1  
    j loop  
    
    5 là tham số truyền vào.

Lệnh rẽ nhánh

Lệnh rẽ nhánh sẽ thực hiện 2 thao tác: so sánh và nhảy khi thỏa điều kiện.

Có 2 lệnh rẽ nhánh là

loop:
    addi $s0, $s0, 1
    j loop

6 (branch if equal) và

loop:
    addi $s0, $s0, 1
    j loop

7 (branch if not equal). Cú pháp:

<Tên lệnh> <thanh ghi 1>, <thanh ghi 2>, <địa chỉ hoặc nhãn>

Lệnh

loop:
    addi $s0, $s0, 1
    j loop

6 sẽ so sánh giá trị trong 2 thanh ghi, nếu bằng nhau thì nhảy đến nhãn chỉ định. Lệnh

loop:
    addi $s0, $s0, 1
    j loop

7 thì ngược lại, nhảy khi 2 giá trị khác nhau. Khi không nhảy, chương trình sẽ thực hiện lệnh tiếp theo.

Địa chỉ truyền vào là địa chỉ tương đối và có dấu, PC sẽ được tính lại như sau:

<Tên lệnh> <thanh ghi 1>, <thanh ghi 2>, <địa chỉ hoặc nhãn>

0, với

loop:
    addi $s0, $s0, 1
    j loop

5 là địa chỉ truyền vào.

Để so sánh lớn hơn/bé hơn, MIPS đưa thêm lệnh

<Tên lệnh> <thanh ghi 1>, <thanh ghi 2>, <địa chỉ hoặc nhãn>

2 (set on less than). Cú pháp:

Với rt, rs, rd là các thanh ghi. Lệnh này sẽ gán

<Tên lệnh> <thanh ghi 1>, <thanh ghi 2>, <địa chỉ hoặc nhãn>

3 bằng 1 khi

<Tên lệnh> <thanh ghi 1>, <thanh ghi 2>, <địa chỉ hoặc nhãn>

4, bằng 0 trong trường hợp ngược lại.

So sánh trong lệnh trên là so sánh có dấu (bù 2). Để so sánh không dấu, MIPS hỗ trợ lệnh

<Tên lệnh> <thanh ghi 1>, <thanh ghi 2>, <địa chỉ hoặc nhãn>

5, cách dùng tương tự như trên.

Ngoài ra, cũng có lệnh để so sánh với một hằng số, là

<Tên lệnh> <thanh ghi 1>, <thanh ghi 2>, <địa chỉ hoặc nhãn>

6 và

<Tên lệnh> <thanh ghi 1>, <thanh ghi 2>, <địa chỉ hoặc nhãn>

7. Cú pháp tương tự như các lệnh tính toán với hằng số ở trên.

Kết hợp các lệnh đã tìm hiểu, ta có thể dịch đoạn chương trình C sau sang hợp ngữ MIPS:

int n = 1000;
int s = 0;
for (int i=1; i<n; i++) s += i;
<tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

0

Thủ tục trong hợp ngữ

Trong hợp ngữ, sử dụng thủ thục thực chất là nhảy đến đoạn code của thủ tục đó. Tuy nhiên có một số vấn đề phát sinh:

  • Làm thế nào để biết lệnh nào được thực thi sau khi kết thúc thủ tục?
  • Truyền các tham số vào thủ tục như thế nào?
  • Thanh ghi nào để lưu giá trị trả về?
  • Quản lý việc sử dụng thanh ghi giữa các thủ tục như thế nào? Vì thủ tục được gọi có thể thay đổi các thanh ghi được dùng trong thủ tục gọi.

MIPS giải quyết các vấn đề này bằng một số quy ước, khi lập trình ta nên tuân thủ theo các quy ước này để code có tính tái sử dụng cao và hạn chế sai lầm từ người lập trình.

Vị trí quay về

Ví dụ đoạn code C sau được dịch sang hợp ngữ ở dưới:

<tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

1

<tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

2

Sau khi thực hiện xong hàm

<Tên lệnh> <thanh ghi 1>, <thanh ghi 2>, <địa chỉ hoặc nhãn>

8 thì chương trình sẽ nhảy về L1 để tiếp tục thực thi hàm

<Tên lệnh> <thanh ghi 1>, <thanh ghi 2>, <địa chỉ hoặc nhãn>

9. Nhưng chuyện xảy ra nếu có một hàm khác cũng gọi

<Tên lệnh> <thanh ghi 1>, <thanh ghi 2>, <địa chỉ hoặc nhãn>

8:

<tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

3

Ta thấy nếu hàm

int n = 1000;
int s = 0;
for (int i=1; i<n; i++) s += i;

1 gọi

<Tên lệnh> <thanh ghi 1>, <thanh ghi 2>, <địa chỉ hoặc nhãn>

8 thì địa chỉ nhảy về sẽ khác với khi

<Tên lệnh> <thanh ghi 1>, <thanh ghi 2>, <địa chỉ hoặc nhãn>

9 gọi

<Tên lệnh> <thanh ghi 1>, <thanh ghi 2>, <địa chỉ hoặc nhãn>

8.

Để giải quyết vấn đề này, MIPS cung cấp lệnh

j <đỉa chỉ cần nhảy tới hoặc nhãn>

8 (jump and link).

j <đỉa chỉ cần nhảy tới hoặc nhãn>

8 sẽ gán giá trị thanh ghi

int n = 1000;
int s = 0;
for (int i=1; i<n; i++) s += i;

7 bằng với địa chỉ của lệnh tiếp theo trước khi thực hiện nhảy. Như vậy, sau khi thực hiện xong, hàm được gọi chỉ cần

int n = 1000;
int s = 0;
for (int i=1; i<n; i++) s += i;

8 để nhảy về đúng lệnh cần nhảy:

<tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

4

$sp - Bộ nhớ stack

Giả sử có 3 hàm gọi nhau, sử dụng

int n = 1000;
int s = 0;
for (int i=1; i<n; i++) s += i;

7 như trên:

<tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

5

<tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

6

Ta thấy ngay 2 vấn đề:

  • Thanh ghi

    int n = 1000; int s = 0; for (int i=1; i<n; i++) s += i;

    7 sẽ bị thay đổi khi

    <tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

    01 gọi

    <tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

    02, vì vậy sau đó

    <tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

    01 sẽ không trả về đúng địa chỉ nữa.
  • Thanh ghi

    lui $t0, 0x1234 ori $t0, $t0, 0x5678 add $s0, $s0, $t0

    6 bị thay đổi trong hàm khác, vì thế code sẽ không chạy đúng như mong muốn nữa.

Để giải quyết vấn đề này, MIPS đưa ra một số thỏa hiệp giữa hàm gọi (caller - R) và hàm được gọi (callee - E):

  • Đối với các thanh ghi

    lui $t0, 0x1234 ori $t0, $t0, 0x5678 add $s0, $s0, $t0

    6 -

    <tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

    06 và

    <tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

    07, E phải khôi phục lại đúng giá trị ban đầu sau khi thực thi xong.
  • Đối với các thanh ghi khác:

    <tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

    08,

    <tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

    09,

    <tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

    10,

    int n = 1000; int s = 0; for (int i=1; i<n; i++) s += i;

    7, E có quyền thay đổi giá trị các thanh ghi này, vì vậy R có trách nhiệm sao lưu và khôi phục lại các thanh ghi này trước và sau khi gọi E (nếu cần sử dụng).

Để quản lý sao lưu / khôi phục các thanh ghi như yêu cầu ở trên, ta dùng bộ nhớ stack.

Trong MIPS, thanh ghi

<tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

07 có giá trị trỏ tới đỉnh stack. Ở đầu hàm, ta lưu các biến cần sao lưu vào stack, sau đó ở cuối hàm, ta khôi phục lại các biến đó. Viết lại đoạn chương trình trên như sau:

<tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

7

Truyền tham số - giá trị trả về

4 thanh ghi

<tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

13 đến

<tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

14 được quy ước dùng riêng cho các tham số truyền vào. Và 2 thanh ghi

<tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

15,

<tên lệnh> <thanh ghi đích>, <thanh ghi>, <hằng số>

16 được dùng cho giá trị trả về. Ví dụ: