You are on page 1of 26

1.

Giới thiệu PCSPim


PCSpim là chương trình chạy và mô phỏng chương trình hợp ngữ dành cho MIPS. Sau
khi cài đặt PCSpim và chạy chương trình (Start -> All Programs -> PCSpim.exe), cửa sổ
chương trình PCSpim hiện lên như hình 1.

Hình 1: Cửa sổ chương trình PCSpim

Cửa sổ hiển thị chia làm 4 phần:

• Phần trên cùng hiển thị nội dung của 32 thanh ghi của CPU và FPU. Nội dung sẽ
tự động cập nhật khi chương trình hợp ngữ chạy.
• Phần dưới kế tiếp hiển thị mã của chương trình ở dạng hợp ngữ, dạng mã máy (số
hex cột thứ 2 từ trái qua), và địa chỉ tương ứng của mỗi lệnh (cột đầu tiên bên trái).
• Phần dưới kế tiếp hiển thị dữ liệu khai báo trong chương trình hợp ngữ (ví dụ:
mảng hay chuỗi) và dữ liệu trong vùng ngăn xếp khi chương trình hợp ngữ được
thực thi.
• Phần dưới cùng hiển thị các thông tin phụ của SPIM, thông tin về lỗi nếu có.
Chương trình hợp ngữ muốn chạy được phải được load trước. Đế load chương trình hợp
ngữ (ở dạng một file có đuôi mở rộng là *.asm hay *.s), thực hiện thao tác File menu ->
Open -> chọn file chứa chương trình cần load. Để tạo file *.asm, chúng ta có thể dùng các
chương trình soạn thảo thô như Notepad, Notepad++, EditPlus…
Hình 2. Load file chứa chương trình nguồn.

Sau khi chương trình hợp ngữ đã được load, chúng ta


có thể thực hiện chạy chương trình và quan sát sự thay
đối giá trị các thanh ghi, các ô nhớ, vị trí và lệnh đang
được thực thi … Các tác vụ chạy chương trình ở trong
Simulator menu.

Các bước để chạy và quan sát quá trình chạy của


chương trình hợp ngữ trên PCSpim:

• Chọn Simulator -> Breakpoints… (hoặc nhấn


Ctrl+B). Cửa sổ hiện ra điền vào textbox
Address giá trị 0x00400000, chọn Add.
• Chọn Simulator -> Go (hoặc nhấn F5). Điền
giá trị 0x00400000 vào texbox Starting
Address, chọn OK. Giá trị của thanh ghi PC
lúc này là 0x00400000.
• Chọn Simulator -> Single Step (hoặc nhấn F10) để chạy từng dòng lện trong
chương trình. Chúng ta có thể quan sát kết quả thực hiện thông qua giá trị của các
thanh ghi, các ô nhớ liên quan.
Cách khác để chạy chương trình nguồn: Simularor -> SetValue…, cửa sổ hiện ra, điền
vào textbox Register Name giá trị PC, điền vào textbox Value giá trị 0x00400000. Sau đó
nhấn F10 để chạy từng bước hay F5 để chạy cho đến khi gặp breakpoints.

2. Các kiến thức cơ sở


2.1 Đổi qua lại giữ số thập lục phân (hexadecimal) và số nhị phân (binary)

Số thập lục phân được tạo thành từ 16 ký số: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f. Số


nhị phân tạo thành từ 2 ký số: 0, 1. Một ký số thập lục phân tương ứng với số nhị phân gồm 4
ký số theo bảng sau:

Ví dụ:
Số thập lục phân 0xeca86420 chuyển sang số nhị phân:

Số nhị phân 1 0011 0101 0111 1001 1011 1101 1111 chuyển sang số thập lục phân:

2.2 Tổ chức bộ nhớ

Bộ nhớ là một mảng 232 ô nhớ


8-bit, bắt đầu từ địa chỉ 0x000000
đến địa chỉ 0xFFFFFFFF. Người
dùng chỉ có thể sử dụng vùng nhớ
từ 0x00400000 đến 0x7FFFFFFF.
Vùng nhớ người dùng chia làm 3
phần: vùng chứa mã (text segment),
vùng chứa dữ liệu (data segment)
và vùng nhớ stack (stack segment). Bộ xử lý tương tác với bộ nhớ thông qua việc di chuyển
dữ liệu giữa các ô nhớ và các thanh ghi. Có hai thao tác để thực hiện việc di chuyển dữ liệu
giữa bộ nhớ và thanh ghi: load và store. Dữ liệu di chuyển được tổ chức thành nhóm một,
hai hay bốn byte liên tục nhau theo chiều tăng địa chỉ ô nhớ.

• load: nhóm byte liên tục nhau theo chiều tăng địa chỉ ô nhớ bắt đầu từ địa chỉ
được chỉ định được copy vào thanh ghi.

• store: nhóm dữ liệu 1, 2 byte thấp của thanh ghi hay cả 4 byte của thanh ghi được
copy vào bộ nhớ từ địa chỉ được chỉ định.

2.3 Tổ chức thanh ghi

MIPS có tấc cả 32 thanh ghi 32-bit có thê sử dụng trong ngôn ngữ assembly được liệt kê
trong bảng sau:

Bộ xử lý MIPS có bộ tính toán số học/luận lý (ALU) bên trong thực hiện các phép toán
số học và luận lý trên số nguyên 32-bit. Phép toán thực
hiện bởi ALU gồm hai toán hạng. Một toán hạng là số
nguyên chứa trong thanh ghi, toán hạng còn lại có thể
chứa trên thanh ghi hay là một phần của lệnh thực hiện
phép toán (hằng số). Kết quả của phép toán luôn đặt vào
thanh ghi.
3. Tập lệnh của MIPS
Lệnh Cú pháp Định dạng lệnh Ý nghĩa

Cộng số bù 2 add rd, rs, rt rd <-- rs+rt ;

Cộng không dấu addu rd, rs, rt rd <-- rs+rt ;

Cộng với hằng số addi rd, rs, const rd Å rs+const ;


16 bit bù 2

Cộng với hằng số addiu rd, rs, const rd Å rs+const ;


16 bit không dấu

AND từng bit and rd, rs, rt rd Å bitwise AND of rs with rt

AND từng bit với andi rd, rs, const rd Å bitwise AND of rs with
hằng số 16 bit const

Nhảy nếu bằng beq rs, rt, addr nhảy đến addr nếu rs == rt. Cần
phải thêm delay theo sau (nop)

Nhảy nếu lớn hơn bgez rs, addr nhảy đến addr nếu số bù 2 trong
hay bằng 0 rs >= 0. Cần thêm delay

Nhảy nếu nhỏ hơn bltz rs, addr nhảy đến addr nếu số bù 2 trong
0 rs < 0. Cần thêm delay

Nhảy nếu khác bne rs, rt, addr nhảy đến addr nếu rs != rt. Cần
thêm delay

Chia hai số bù 2 div rs, rt lo Å rs div rt; hi Å rs mod rt


hai số rs và rt ở dạng bù 2

Chia hai số không divu rs, rt lo Å rs div rt; hi Å rs mod rt


dấu hai số rs và rt ở dạng không dấu

Nhảy không điều j target delay sau một chu kỳ máy:


kiện PC Å địa chỉ của target

Nhảy không điều jal target gọi hàm target, $ra = PC +4


kiện và link PC Å địa chỉ của target, thêm
nop

Nhảy không điều jr rs trở về hàm gọi, thường dùng


kiện theo giá trị jr $ra , PC Å $ra, cần thêm lệnh
thanh ghi delay nop theo sau

Load byte và mở lb rd, offset(base) rd Å byte đã được mở rộng dấu


rộng dấu từ ô nhớ có địa chỉ base +
offset, offset là số bù 2

Load byte và mở lbu rd, offset(base) rd Å byte đã được mở rộng 0 từ


rộng số 0 đầu ô nhớ có địa chỉ base + offset,
offset là số bù 2

Load half-word lh rd, offset(base) rd Å 2 byte liên tiếp đã được


và mở rộng dấu mở rộng dấu từ ô nhớ có địa chỉ
base + offset , offset là số bù 2

Load half-word lhu rd, offset(base) rd Å 2 byte liên tiếp đã được


và mở rộng số 0 mở rộng 0 từ ô nhớ có địa chỉ
base + offset, offset là số bù 2

Load hằng vào 16 lui rd, const 2 byte cao của rd Å 16 bit const
bit cao 2 byte thấp của rd Å 0x0000

Load word lw rd, offset(base) rd Å word bộ nhớ có địa chỉ


base + offset, offset là số bù 2

Chuyển giá trị từ mfhi rd rd Å hi


hi vào thanh ghi

Chuyển giá trị từ mflo rd rd Å lo


lo vào thanh ghi

Nhân hai số bù 2 mult rs, rt value(hi, lo) Å rs*rt; rs và rt là


hai số bù 2

Nhân hai số multu rs, rt value(hi, lo) Å rs*rt; rs và rt là


không dấu hai số không dấu

NOT từng bit nor rd, rs, $0 rd Å NOT từng bit của rs

NOR từng bit nor rd, rs, rt rd Å NOR từng bit của rs và rt

OR từng bit or rd, rs, rt rd Å OR từng bit của rs và rt

OR từng bit với or rd, rs, const rd Å OR từng bit của rs và


hằng 16 bit hằng sau khi mở rộng 0

Lưu byte thấp của sb rs, offset(base) byte ở offset +base Å byte thấp
thanh ghi vào bộ của rs, offset dạng bù 2
nhớ

Lưu hai byte thấp sh rs, offset(base) 2 byte ở offset +base Å 2 byte
vào bộ nhớ thấp của rs, offset dạng bù 2

Lệnh nop sll $0, $0, 0 tạo thời gian trễ cần thiết
Dịch trái không sll rd, rs, shft rd Å rs sau khi dich trái shft
dấu bit; 0 <= shft <32

So sánh hai thanh slt rd, rs, rt if rs < rt


ghi dạng bù 2 rd <-- 1
else
rd <-- 0
hai toán hạng dạng bù 2

So sánh thanh ghi slti rd, rs, const if rs < const


với hằng bù 2 rd <-- 1
else
rd <-- 0
hai toán hạng dạng bù 2

So sánh thanh ghi sltiu rd, rs, const if rs < const


với hằng không rd <-- 1
dấu else
rd <-- 0
hai toán hạng dạng không dấu

So sánh hai thanh sltu rd, rs, rt if rs < rt


ghi không dấu rd <-- 1
else
rd <-- 0
hai toán hạng dạng không dấu

Dịch phải có dấu sra rd, rs, shft rd Å rs sau khi dich phải shft
bit và mở rộng dấu;
0 <= shft <32

Dịch phải luận lý srl rd, rs, shft rd Å rs sau khi dich phải shft
bit; 0 <= shft <32

Trừ hai thanh ghi sub rd, rs, rt rd Å rs – rt; các toán hạng dạng
dạng bù 2 bù 2

Trừ hai thanh ghi subu rd, rs, rt rd Å rs – rt; các toán hạng dạng
dạng không dấu không dấu

Lưu thanh ghi vào sw rs, offset(base) word ở địa chỉ offset + base Å
bộ nhớ $rs; offset dạng bù 2

XOR từng bit xor rd, rs, rt rd Å XOR từng bit rs và rt

XOR từng bit với xori rd, rs, rt rd Å XOR từng bit rs và hằng
hằng sau khi mở rộng 0
4. Cú pháp của MIPS Assempler
Chú thích (comment) chuỗi các từ bắt đầu bằng #, tất cả các từ bắt đầu từ # cho đến cuối
dòng đều được bỏ qua.
Tên định danh (identifier) là chuỗi các ký tự chữ, số, dấu gạch dưới (_) và dấu chấm (.)
không bắt đầu bằng số. Tên không được trùng với các từ dành riêng là opcode của lệnh.
Các ví dụ về tên định danh hợp lệ:
main, loop, end_if, case1.2
Các ví dụ về tên định danh không hợp lệ:
1value # số đứng đầu
b # trùng với opcode lệnh nhảy
add # trùng với opcode lệnh cộng
Nhãn bao gồm tên định danh theo sau là dấu hai chấm (:) được đặt ở đầu dòng.
Ví dụ:
.data
item: .word 1
.text
.globl main # must be global
main:

Số (number) mặc định là cơ số 10. Số thập lục phân (hexadecimal) thêm 0x vào phía
trước. Hai số 256 và 0x100 diễn tả số có cùng giá trị.
Chuỗi (string) được đặt giữa hai dấu nháy kép (“). Ví dụ: “Hello world!\n”. Các ký tự
đặt biệt cho phép trong chuỗi:
newline \n
tab \t
quote \”
Chỉ thị (directive) được hỗ trợ gồm có:

Tên chỉ thị Ý nghĩa


.text <addr> Tất cả các phần theo sau cho tới chỉ thị mới (.text, .ktext, .data, .kdata) được
đặt trong vùng nhớ chương trình (code segment). Tham số addr nếu có quy
định địa chỉ bắt đầu của vùng nhớ chương trình dùng để lưu các phần trong
phân đoạn mã chương trình này. Phần theo sau thường là các lệnh.
.ktext <addr> Tất cả các phần theo sau cho tới chỉ thị mới (.text, .ktext, .data, .kdata) được
đặt vào vùng nhớ nhân (kernel) của hệ điều hành. Tham số addr nếu có quy
định địa chỉ bắt đầu của vùng nhớ dùng để lưu. Phần theo sau thường là các
lệnh.
.globl sym Khai báo nhãn sym là toàn cục và có thể được tham khảo từ file khác
.data <addr> Tất cả các phần theo sau cho tới chỉ thị mới (.text, .ktext, .data, .kdata) được
đặt trong vùng nhớ dữ liệu nhân (kernel data segment). Tham số addr nếu
có quy định địa chỉ bắt đầu của vùng nhớ dùng để lưu.
.kdata <addr> Tất cả các phần theo sau cho tới chỉ thị mới (.text, .ktext, .data, .kdata) được
đặt trong vùng nhớ dữ liệu (data segment). Tham số addr nếu có quy định
địa chỉ bắt đầu của vùng nhớ dùng để lưu.
.ascii str Lưu chuỗi str vào bộ nhớ, không có ký tự kết thúc chuỗi (giá trị = 0) sau
cùng
.asciiz str Lưu chuỗi str vào bộ nhớ, thêm ký tự kết thúc chuỗi (giá trị = 0) sau cùng
.byte b1,..,bn Lưu n byte liên tiếp nhau b1,..,bn vào bộ nhớ
.half h1,..,hn Lưu n phần tử 16-bit liên tiếp nhau h1,..,hn vào bộ nhớ
.word w1,..,wn Lưu n phần tử 32-bit liên tiếp nhau w1,..,wn vào bộ nhớ
.float f1,..,fn Lưu n số thực dấu chấm động độ chính xác đơn liên tiếp nhau f1,..,fn vào bộ
nhớ
.double d1,..,dn Lưu n số thực dấu chấm động độ chính xác đơn liên tiếp nhau d1,..,dn vào
bộ nhớ
.space n Cấp phát n byte liên tiếp nhau trong phân đoạn dữ liệu hiện tại. Phải đặt sau
chỉ thị .data
.extern sym n Khai báo dữ liệu lưu ở sym có kích thước n byte và sym là nhãn toàn cục.
Dữ liệu vùng nhớ này được truy xuất thông qua thanh ghi $gp
5. Các chương trình mẫu
5.1 Các lệnh luận lý:
and ori nor
andi xor sll
or xori srl

Thiết lập các tham số trong menu Simulator -> Settings:


Bare Machine ON, Allow Pseudo Instructions OFF, Load Trap File OFF, Delayed
Branches ON, Delayed Loads ON, Mapped IO OFF, Quiet OFF
## Put the bit pattern 0x0000FACE into register $1.
.text
.globl main
main:
ori $1,$0,0xFACE # 0x0000FACE into $1

andi $2,$1,0x0F0F # 0x0A0E in $2

andi $3,$1,0x00F0 # 0x00C0 in $3


sll $3,$3,8 # 0xC000 in $3
or $2,$2,$3 # 0xCA0E in $2

andi $3,$1,0xF000 # 0xF000 in $3


srl $3,$3,8 # 0x00F0 in $3
or $2,$2,$3 # 0xCAFE in $3
# done
## End of file

5.2 Các lệnh số học:


add addu div multu
addi sub divu mfhi
addiu subu mult mflo

Thiết lập các tham số trong menu Simulator -> Settings:


Bare Machine ON, Allow Pseudo Instructions OFF, Load Trap File OFF, Delayed
Branches ON, Delayed Loads ON, Mapped IO OFF, Quiet OFF
## Program to calculate 3x**2 + 5x - 8
##
## Assumes that all results fit into 32 bits.
##
## Follows the hardware rule of keeping a new mult
## two instructions away from a previous mflo.
##
## Register Use:
## $8 x
## $9 result

.text
.globl main

main:
addiu $8, $0, 1 # put x into $8
mult $8, $8 # lo = x**2
mflo $9 # $9 = x**2
ori $7, $0, 3 # $7 = 3
ori $6, $0, 5 # $6 = 5
mult $7, $9 # lo = 3x**2
mflo $9 # $9 = 3x**2
ori $7, $0, 5 # $7 = 5
addi $9, $9, -8 # $9 = 3x**2 - 8
mult $7, $8 # lo = 5x
mflo $7 # $7 = 5x
addu $9, $9, $7 # $9 = 3x**2 + 5x - 8

## End of file

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


lb lui sb
lbu lw sh
lh lhu sw

Thiết lập các tham số trong menu Simulator -> Settings:


Bare Machine ON, Allow Pseudo Instructions OFF, Load Trap File OFF, Delayed
Branches ON, Delayed Loads ON, Mapped IO OFF, Quiet OFF
## copy $9 to memory in big-endian form
##
## Register Use:
## $8 --- first byte of the tape block
## $9 --- 4-byte integer

.text
.globl main

main:
lui $9,0x1234 # put data in $9
ori $9,0x5678 #
lui $8,0x1000 # $8 is base register
sb $9,3($8) # least significant byte
srl $9,$9,8 # move next byte to low order
sb $9,2($8) # bits 8-15
srl $9,$9,8 # move next byte to low order
sb $9,1($8) # bits 16-23
srl $9,$9,8 # move next byte to low order
sb $9,0($8) # most significant byte

.data
tape: # base register points here
.space 1024 # tape buffer (1K bytes)

## End of file

5.4 Các lệnh nhảy


j bne slti
beq bltz sltu
bgez slt sltiu

Thiết lập các tham số trong menu Simulator -> Settings:


Bare Machine ON, Allow Pseudo Instructions OFF, Load Trap File OFF, Delayed
Branches ON, Delayed Loads ON, Mapped IO OFF, Quiet OFF
## Find the Significant bits in a pattern
## The significant bits are the leftmost one bit and
## all its to its right
## Approach: Count the number of right shifts needed
## before the pattern becomes all zeros
##
## Register Use:
## $7 --- shift count
## $8 --- the bit pattern, 0x00298D7D for example.

.text
.globl main

main:
ori $8,$0,0x0029 # load $8 with the pattern
sll $8,$8,16 # shift into MSBs
ori $8,$8,0x8D7D # or in the LSBs
loop: beq $8,$0,exit # done when pattern == 00
sll $0,$0,0 # delay or nop after a branch
# or jump instruction
srl $8,$8,1 # shift right one bit
addu $7,$7,1 # increment shift count

j loop # repeat
sll $0,$0,0

exit: j exit # sponge for extra cycles


sll $0,$0,0

## End of file

5.5 Các ví dụ xử lý chuỗi, xử lý mảng


Các lệnh đã biết cho đến giờ:
add div mflo slt, slti
addi divu mult sltu, sltiu
addiu j multu sra
addu lb nor srl
and lbu or sub
andi lh ori subu
beq lhu sb sw
bgez lui sh xor
bltz lw sll xori
bne mfhi

Thiết lập các tham số trong menu Simulator -> Settings:


Bare Machine ON, Allow Pseudo Instructions OFF, Load Trap File OFF, Delayed
Branches ON, Delayed Loads ON, Mapped IO OFF, Quiet OFF
## To Lower Case
##
##
## Write a program that converts the string to all lower case
## characters. Do this by adding 0x20 to each character in the string.

## Register Use:
##
## $8 --- current character
## $10 --- character pointer
.text
.globl main

main: lui $10,0x1000 # initialize base register of the string

lbu $8,($10) # get the first char of the string


sll $0,$0,0
loop:
beq $8,$0,halt # while ( char != '/0' )
sll $0,$0,0 #
addiu $8,$8,0x20 # uncapitalize char
sb $8,($10) # replace char in string
addiu $10,$10,1 # advance the char pointer
lbu $8,($10) # get the next char of the string
j loop # end while
sll $0,$0,0

halt: j halt # cycle sponge


sll $0,$0,0

.data
string: .asciiz "ABCDEFGHIJKLMNOP"

## End of file

5.6 Các lệnh mã giả mở rộng


move d,s # copy the contents of the source register s to the
# destination register d

li d,value # load register $d with the positive or negative


# integer "value". Value may be a 16 or a 32-bit integer.

lw d,exp # Load register $d with the value at address "exp".


# "exp" is often a symbolic address.

la d,exp # load register $d with the address described by the


# expression "exp". "exp" is often a symbolic address.

nop # no operation. do nothing for one machine cycle.

sw d,exp # Store register $d into the word at address exp.


# exp can be any of several expression types
# that evaluate to an address
mul d,s,t # multiply $s by $t. put the result in $d

div d,s,t # divide $s by $t. Put the quotient in $d. Operands are
# two's complement.
divu d,s,t # divide $s by $t. Put the quotient in $d. Operands are
# unsigned.
remu d,s,t # divide $s by $t. Put the remainder in $d. Operands are
# unsigned.

Thiết lập các tham số trong menu Simulator -> Settings:


SPIM set Bare Machine OFF, Allow Pseudo Instructions ON, Load Trap File OFF,
Delayed Branches ON, Delayed Loads ON, Mapped IO OFF, Quiet OFF.
Ví dụ theo sau thực hiện việc tính hóa đơn bữa ăn:
tổng phí = tiền thức ăn + 8% tiền thuế +15% tiền phục vụ
## rest.asm
##
## Total restaurant bill calculator
##
## Register Use:
##
## $s0 meal cost
## $s1 tip rate
## $s2 tax rate
## $s3 total rate
## $s4 tax+tip dollars
## $s5 total bill

.globl main

# Get meal cost


main: li $v0,4 # print prompt
la $a0,prompt
syscall
li $v0,5 # input meal cost
syscall
move $s0,$v0 # save it in $s0

# Calculations
lw $s1,tip # get tip rate
lw $s2,tax # get tax rate
addu $s3,$s1,$s2 # (tax + tip) in percent
mul $s4,$s0,$s3 # mealcost*(total rate)
div $s4,$s4,100 # mealcost*(total rate)/100
addu $s5,$s0,$s4 # total bill

# Output
li $v0,4 # print string
la $a0,head1 # "tax plus tip"
syscall

move $a0,$s4 # get tax+tip


li $v0,1 # print integer
syscall #

li $v0,4 # print string


la $a0,head2 # "total cost"
syscall

move $a0,$s5 # get total


li $v0,1 # print integer
syscall #

li $v0,10 # exit
syscall

.data
tip: .word 15 # tip rate in percent
tax: .word 8 # tax rate in percent

prompt: .asciiz "Enter food cost: "


head1 : .asciiz " Tax plus tip: "
head2 : .asciiz "\n Total cost: "
5.7 Các chương trình xử lý xuất nhập
SPIM cung cấp các hàm hệ thống dùng để xử lý các thao tác xuất nhập sau:
Công việc Mã trong $v0 Tham số Giá trị trả về

print integer 1 $a0 == integer

print float 2 $f12 == float

print double 3 ($f12, $f13) == double

print string 4 $a0 == address of string

read integer 5 $v0 == integer

read float 6 $f0 == float

read double 7 ($f0, $f1) == double

read string 8 $a0 == buffer address

$a1 == buffer length

allocate memory 9 $a0 == number of bytes $v0 == address

exit 10

Sau đây là ví dụ sử dụng hàm hệ thống để in chuỗi ký tự và kết thúc chương trình:
# hello.asm
#
.text
.globl main
main:
li $v0,4 # code 4 == print string
la $a0,string # $a0 == address of the string
syscall # Invoke the exception handler.

li $v0,10 # code 10 == exit


syscall # Halt the program.

.data
string: .asciiz "Hello SPIM!\n"
# end of file

Thiết lập các tham số trong menu Simulator -> Settings:


Bare Machine OFF, Allow Pseudo Instructions ON, Load Trap File ON, Delayed
Branches ON, Delayed Loads ON, Mapped IO OFF, Quiet OFF
Ví dụ sau in ra mẫu thư nhắc nhở trả sách một cách tự động sau khi người dùng nhập tên
người mượn sách trả trễ:
# overdue.asm

.text
.globl main

main:
# get patron name
li $v0,4 # print prompt
la $a0,prompt #
syscall
li $v0,8 # code 8 == read string
la $a0,name # $a0 == address of buffer
li $a1,24 # $a1 == buffer length
syscall # Invoke the operating system.

# print the letter


li $v0,4 # print greeting
la $a0,letter #
syscall
li $v0,4 # print body
la $a0,body #
syscall

li $v0,10 # exit
syscall

.data
prompt: .asciiz "enter name, followed by comma-enter: "
letter: .ascii "\n\nDear "
name: .space 24

body: .ascii "\nYour library books are way\n"


.ascii "overdue. Please return them\n"
.ascii "before we give your name\n"
.ascii "to the enforcement squad.\n"

# end of file

5.8 Dùng stack tính toán biểu thức


Thanh ghi stack pointer ($sp) dùng để quản lý stack. Thanh ghi này chỉ đến phần tử trên
đỉnh ở vùng nhớ stack. Khi chương trình bắt đầu chạy, $sp có giá trị khởi tạo
0x7FFFFFFC. Vùng nhớ stack mở rộng xuống dưới đồng nghĩa giá trị thanh ghi $sp
giảm đi.
Tác vụ PUSH phần tử vào stack tiến hành hai việc:
• Thứ nhất, thay đổi giá trị thanh ghi $sp để trỏ đến phần tử đỉnh mới
• Thứ hai, lưu giá trị vào vị trí đỉnh mới
Tương tự, tác vụ POP phần tử ra stack tiến hành hai việc:
• Thứ nhất, lưu giá trị phần tử đỉnh stack vào biến
• Thứ hai, thay đổi giá trị thanh ghi $sp trỏ đến phần tử đỉnh mới
# PUSH the item in $t0:
addiu $sp,$sp,-4 # point to the place for the new item,
sw $t0,($sp) # store the contents of $t0 as the new top.

# POP the item into $t0:


lw $t0,($sp) # Copy top the item to $t0.
addiu $sp,$sp,4 # Point to the item beneath the old top.

Thiết lập các tham số trong menu Simulator -> Settings:


Bare Machine OFF, Allow Pseudo Instructions ON, Load Trap File ON, Delayed
Branches ON, Delayed Loads ON, Mapped IO ON, Quiet OFF
# Evaluate the expression ab - 12a + 18b - 7
#
# Settings: Load delays OFF; Branch delays OFF,
# Trap file ON; Pseudoinstructions ON

.globl main
main:
lw $t0,a # get a
lw $t1,bb # get b
mul $t0,$t0,$t1 # a*b
subu $sp,$sp,4 # push a*b onto stack
sw $t0,($sp)

lw $t0,a # get a
li $t1,-12 #
mul $t0,$t0,$t1 # -12a
subu $sp,$sp,4 # push -12a onto stack
sw $t0,($sp)

lw $t0,bb # get b
li $t1,18 #
mul $t0,$t0,$t1 # 18b
subu $sp,$sp,4 # push 18b onto stack
sw $t0,($sp)
li $t1,-7 # init sum to -7
lw $t0,($sp) # pop 18b
addu $sp,$sp,4
addu $t1,$t1,$t0 # 18b -7

lw $t0,($sp) # pop -12a


addu $sp,$sp,4
addu $t1,$t1,$t0 # -12a + 18b -7

lw $t0,($sp) # pop ab
addu $sp,$sp,4
addu $t1,$t1,$t0 # ab - 12a + 18b -7

done: li $v0,1 # print sum


move $a0,$t1
syscall
li $v0,10 # exit
syscall

.data
a: .word 0
bb: .word 10

5.9 Cách gọi hàm đơn giản


Lệnh jal dùng để gọi hàm, lệnh jr dùng để trở về từ hàm được gọi. Lệnh nop cần được
thêm vào sau các lệnh jal và jr
jal sub # $ra <― PC+4 (the address 8 bytes away from the jal)
# PC <― sub load the PC with the subroutine entry point
# a branch delay slot follows this instruction
jr $ra # PC <― $ra
# A branch delay slot follows this instruction.

Các chú ý về cách gọi hàm đơn giản:

o Thủ tục con được gọi bởi lệnh jal.


o Thủ tục con không gọi thủ tục con khác.
o Thủ tục con trở về chương trình gọi bằng lệnh jr $ra.
o Các thanh ghi được sử dụng:
ƒ $t0 - $t9 — Thủ tục con tự do sử dụng.
ƒ $s0 - $s7 — Thủ tục con không được thay đổi giá trị sau khi trở về.
ƒ $a0 - $a3 — Chứa các tham số cho thủ tục con. Thủ tục con có thể thay đổi
các thanh ghi này.
ƒ $v0 - $v1 — Chứa các giá trị trả về từ thủ tục con.
o Thủ tục main trả điều khiển bằng cách sử dụng hàm exit của hệ thống.

Thiết lập các tham số trong menu Simulator -> Settings:


Bare Machine OFF, Allow Pseudo Instructions ON, Load Trap File ON, Delayed
Branches ON, Delayed Loads ON, Mapped IO ON, Quiet OFF
Sau đây là chương trình đọc 3 số nguyên và in ra tổng của chúng:
# read in three integers and print their sum
#

.text
.globl main
main:
jal pread # read first integer
nop # branch delay slot
move $s0,$v0 # save it in $s0
jal pread # read second integer
nop # branch delay slot
move $s1,$v0 # save it in $s1
jal pread # read third integer
nop # branch delay slot
move $s2,$v0 # save it in $s2

addu $s0,$s0,$s1 # compute the sum


addu $a0,$s0,$s2

li $v0,1 # print the sum


syscall

li $v0,10 # exit
syscall

# pread -- prompt for and read an integer


# on entry:
# $ra -- return address
# on exit:
# $v0 -- the integer
.text
.globl pread
pread:
la $a0,prompt # print string
li $v0,4 # service 4
syscall

li $v0,5 # read int


syscall # service 5

jr $ra # return
nop # branch delay slot

.data
prompt:
.asciiz "Enter an integer: "
5.10 Gọi hàm dùng stack
Các chú ý khi một thủ tục gọi một thủ tục con dùng stack:
Gọi thủ tục con (thực hiện bởi chương trình gọi):

1. Push vào stack các thanh ghi $t0-$t9 cần lưu giá trị. Thủ tục con có thể thay đổi các
thanh ghi này.
2. Gán giá trị vào các tham số của thủ tục con $a0-$a3.
3. Gọi thủ tục con sử dụng jal.

Phần đầu thủ tục con (Trong thủ tục con):

4. Nếu thủ tục con này có thể gọi các thủ tục con khác, phải push $ra vào stack.
5. Push vào stack các thanh ghi $s0-$s7 nếu thủ tục con này có thể thay đổi chúng.

Thân thủ tục con:

6. Thủ tục con có thể thay đổi các thanh ghi T, A hoặc S (nếu như S đã được lưu ở bước 5).
7. Nếu thủ tục con này gọi thủ tục con khác thì nó phải tuân thủ các chú ý này.

Phần cuối thủ tục con (Thực hiện trước khi trở về chương trình gọi):

8. Đưa các giá trị cần trả về vào $v0-$v1


9. Pop ra khỏi stack (theo thứ tự ngược) các thanh ghi $s0-$s7 đã lưu ở bước 5.
10. Pop ra khỏi stack địa chỉ trở về $ra nếu thực hiện bước 4
11. Trở về chương trình gọi sử dụng jr $ra.

Phục hồi điều khiển sau khi trở về từ chương trình con (thực hiện bởi chương trình gọi):

12. Pop ra khỏi stack (theo thứ tự ngươc) các thanh ghi $t0-$t9 đã được push vào stack
ở bước 1.

Thiết lập các tham số trong menu Simulator -> Settings:


Bare Machine OFF, Allow Pseudo Instructions ON, Load Trap File ON, Delayed
Branches ON, Delayed Loads ON, Mapped IO ON, Quiet OFF.
Sau đây là ví dụ chương trình yêu cầu người dùng nhập vào 2 số X, Y. Chương trình thực
hiện việc tính toán các biểu thức X*X, X*Y, 5*Y và in ra kết quả lớn nhất từ giá trị của 3
biểu thức trên. Chương trình main gọi thủ tục con maxExp tính và tìm ra giá trị lớn nhất
từ ba biểu thức X*X, X*Y và 5*Y sau khi đã có giá trị X, Y. Thủ tục con maxExp gọi
thủ tục con maxInt hai lần để so sánh X*X với X*Y, kết quả sẽ so sánh với 5*Y để cho ra
kết quả yêu cầu.
## Driver -- main program for the application

.text
.globl main

main:
sub $sp,$sp,4 # push the return address
sw $ra,($sp)
sub $sp,$sp,4 # push $s0
sw $s0,($sp)

la $a0,xprompt # prompt the user


li $v0,4 # service 4
syscall
li $v0,5 # service 5 -- read int
syscall # $v0 = integer
move $s0,$v0 # save x

la $a0,yprompt # prompt the user


li $v0,4 # service 4
syscall
li $v0,5 # service 5 -- read int
syscall # $v0 = integer

# prepare arguments
move $a0,$s0 # x
move $a1,$v0 # y
jal maxExp # maximum expression
nop # returned in $v0
move $s0,$v0 # keep it safe

la $a0,rprompt # output title


li $v0,4 # service 4
syscall

move $a0,$s0 # get maximum


li $v0,1 # print it out
syscall

lw $ra,($sp) # pop $s0


add $s0,$sp,4
lw $ra,($sp) # pop return address
add $sp,$sp,4

jr $ra # return to OS
nop

.data
xprompt: .asciiz "Enter a value for x --> "
yprompt: .asciiz "Enter a value for y --> "
rprompt: .asciiz "The maximum expression is: "
## maxInt -- compute the maximum of two integer arguments
##
## Input:
## $a0 -- a signed integer
## $a1 -- a signed integer
##
## Returns:
## $v0 -- maximum
.text
.globl maxInt
maxInt:
# body
move $v0,$a0 # max = $a0
bgt $a0,$a1,endif # if $a1 > $a0
nop
move $v0,$a1 # max = $a1
endif: # endif
# epilog
jr $ra # return to caller
nop
## maxExp -- compute the maximum of three expressions
##
## Input:
## $a0 -- a signed integer, x
## $a1 -- a signed integer, y
##
## Returns:
## $v0 -- the maximum of x*x, x*y, or 5*y
##
## Registers:
## $s0 -- x*x
## $s1 -- x*y
## $s2 -- 5*y
.text
.globl maxInt
maxExp:
# prolog
sub $sp,$sp,4 # push the return address
sw $ra,($sp)
sub $sp,$sp,4 # push $s0
sw $s0,($sp)
sub $sp,$sp,4 # push $s1
sw $s1,($sp)
sub $sp,$sp,4 # push $s2
sw $s2,($sp)

# body
mul $s0,$a0,$a0 # x*x
mul $s1,$a0,$a1 # x*y
li $t0,5
mul $s2,$t0,$a1 # 5*y

move $a0,$s0 # compute max of x*x


move $a1,$s1 # and x*y
jal maxInt # current max in $v0
nop

move $a0,$v0 # compute max of


move $a1,$s2 # current max, and 5*y
jal maxInt # total max will be in $v0
nop
5.11 Quản lý biến cục bộ và gọi hàm sử dụng stack pointer và frame pointer
Thủ tục con có thể sử dụng stack để tính toán các biểu thức (5.8), thanh ghi sp có thể thay
đổi nên khó sử dụng thanh ghi này để quản lý biến cục bộ cố định. Để quản lý biến cục
bộ một cách dễ dàng, MIPS sử dụng thanh ghi fp làm địa chỉ nền cố định để quản lý biến
cục bộ trong quá trình thực thi.
Hình bên minh họa việc dùng thanh ghi fp để quản lý biến cục bộ với các giả sử sau:
• Thủ tục cha gọi thủ tục con;
• Thủ tục cha sử dụng các thanh ghi tạm $t0,
$t3 trước khi gọi thủ tục con;
• Thủ tục con sử dụng các thanh ghi $s0, $s3,
$s5;
• Thủ tục con gồm 4 biến cục bộ a, b, i, j

Các chú ý khi một thủ tục gọi một thủ tục con dùng stack pointer và frame pointer:

Gọi thủ tục con (thực hiện bởi chương trình gọi):

1. Push vào stack các thanh ghi $t0-$t9 cần lưu giá trị. Thủ tục con có thể thay đổi các thanh
ghi này.
2. Gán giá trị vào các tham số của thủ tục con $a0-$a3.
3. Gọi thủ tục con sử dụng jal.

Phần đầu thủ tục con (Trong thủ tục con):

4. Push $ra vào stack.


5. Push thanh ghi $fp của thủ tục gọi
6. Push vào stack các thanh ghi $s0-$s7 nếu thủ tục con này có thể thay đổi chúng.
7. Khởi tạo $fp = $sp – không gian cần cho biến cục bộ (4*số biến cục bộ).
8. Khởi tạo $sp = $fp

Thân thủ tục con:

9. Thủ tục con có thể thay đổi các thanh ghi T, A hoặc S (nếu như S đã được lưu ở bước 5).
10. Thủ tục con tham khảo đến biến cục bộ sử dụng offset($fp)
11. Thủ tục con tự do push, pop phần tử vào stack
12. Nếu thủ tục con này gọi thủ tục con khác thì nó phải tuân thủ các chú ý này.

Phần cuối thủ tục con (Thực hiện trước khi trở về chương trình gọi):

13. Đưa các giá trị cần trả về vào $v0-$v1


14. $sp = $fp + không gian cần cho biến cục bộ
15. Pop ra khỏi stack (theo thứ tự ngược) các thanh ghi $s0-$s7 đã lưu ở bước 5.
16. Pop ra khỏi stack địa chỉ trở về $ra.
17. Pop ra khỏi stack địa chỉ trở về $ra
18. Trở về chương trình gọi sử dụng jr $ra.

Phục hồi điều khiển sau khi trở về từ chương trình con (thực hiện bởi chương trình gọi):

19. Pop ra khỏi stack (theo thứ tự ngươc) các thanh ghi $t0-$t9 đã được push vào stack ở
bước 1.

Thiết lập các tham số trong menu Simulator -> Settings:


Bare Machine OFF, Allow Pseudo Instructions ON, Load Trap File ON, Delayed
Branches ON, Delayed Loads ON, Mapped IO ON, Quiet OFF.

# main()
# {
# int a, b; // a: 0($fp), b: 4($fp)
# write("enter an int:")
# read( a );
# b = fact( a );
# write("factorial is:")
# print( b );
# }
.text
.globl main
main:
addiu $sp,$sp,-4
sw $ra,0($sp) # 1. Push return address
addiu $sp,$sp,-4
sw $fp,0($sp) # 2. Push caller's frame pointer
# 3. No S registers to push
addiu $fp,$sp,-8 # 4. $fp = $sp - space_for_variables
addu $sp,$fp,$0 # 5. $sp = $fp

li $v0,4 # write("enter an int:")


la $a0,prompt1
syscall

li $v0,5 # read( a )
syscall
# subroutine call
# 1. No T registers to push
addu $a0,$v0,$0 # 2. Put argument into $a0
jal fact # 3. Jump and link to subroutine
nop
# return from subroutine
# 1. No T registers to restore
sw $v0,4($fp) # b = fact( a )
li $v0,4 # write("factorial is:")
la $a0,prompt2
syscall

lw $a0,4($fp) # print( b )
li $v0,1
syscall
# epilog
# 1. No return value
addu $sp,$sp,8 # 2. $sp = $fp + space_for_variables
# 3. No S registers to pop
lw $fp,0($sp) # 4. Pop $fp
lw $ra,4($sp) # 5. Pop $ra
addu $sp,$sp,8

jr $ra # return to OS
nop

.data
prompt1: .asciiz "enter an int:"
prompt2: .asciiz "factorial is:"

# int fact( int n )


# {
# if ( n <= 1 )
# return 1;
# else
# return n*fact(n-1);
# }
.text
.globl fact
fact:
addiu $sp,$sp,-8 # adjust stack for 2 items
sw $ra,0($sp) # save return address
sw $a0,4($sp) # save argument

#addi $t0,$0,2
slti $t0,$a0,2 # test for n < 2
beq $t0,$0,else

addi $v0,$0,1 # if so, result is 1


addiu $sp,$sp,8 # pop 2 items from stack
jr $ra # and return
nop

else:
addi $a0,$a0,-1 # else decrement n
jal fact # recursive call
nop
lw $a0,4($sp) # restore original n
lw $ra,0($sp) # and return address
addi $sp,$sp,8 # pop 2 items from stack

mul $v0,$v0,$a0 # multiply to get result


jr $ra # and return
nop

You might also like