一、Casl汇编语言语法介绍
学习一个汇编语言需要掌握3个要点:CPU 的可编程寄存器结构、寻址方式及指令系统、伪指令。
1、COMETCPU 的可编程寄存器
COMETCPU 字长 16 位,采用从左到右的编号。bit0 在最左边(最高位),bit15 在最右边(最低位)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
1)16 位通用寄存器五个:
GR0、GR1、GR2、GR3、GR4
通用功能:各种算术逻辑运算
特殊功能:除 GR0 外都可作变址寄存器(地址指针)XR,GR0 可看成累加器。
GR4 兼作堆栈指针(SP)
2)指令计数器 PC 一个(16位)
存放正在执行的那条指令的第 1 个字的地址(一条指令占二个字),指令结束时,PC 中存放下一条指令的地址(一般为原指令地址 +2 )。
3)状态寄存器 FR 一个(二位)
运算结果 | FR0 | FR1 |
大于 | 0 | 0 |
等于 | 0 | 1 |
小于 | 1 | 0 |
可以把 FR0 看成 SF(符号位),FR1 看成 ZF(零位位)
除了算术逻辑运算指令(包括移位指令)外,LEA 指令也会影响 FR
2、符号指令写法与寻址方式
OP GR,ADR[,XR]
其中 OP 为操作码;GR 为五个通用寄存器 GR0~GR4 之一;ADR 为一个 16 位的地址码;XR 为四个变址寄存器 GR1~GR4 之一,[ ]表示可以省略。
1)直接寻址:当 [,XR] 省略时,为直接寻址。
2)变址寻址:有效地址 E = ADR +(XR),当 ADR = 0 时,为寄存器间接寻址。
3)立即数寻址:Casl 没有立即数寻址。但在 LEA 指令中,当 [,XR] 省略时,可作立即数传送。没有立即数运算指令。
4)寄存器寻址:Casl 也没有寄存器寻址(对源操作数)。但 LEA 指令当 ADR = 0 时,可作寄存器寻址(只用于数据传送)。
3、Casl指令系统
1)取数指令 LD:内存→寄存器
LD GR,ADR[,XR]
2)存数指令 ST:寄存器→内存
ST GR,ADR[,XR]
3)加法 ADD、减法 SUB、逻辑与 AND、逻辑或OR、逻辑异或 EOR指令:
ADD | ┐ | |
SUB | │ | |
AND | ├ | GR,ADR[,XR] |
OR | │ | |
EOR | ┘ |
内存 OP 寄存器→寄存器
4)算术比较 CPA:
两个带符号数比较,结果不回送,只影响标志位。
CPA GR,ADR[,XR]
5)逻辑比较 CPL:两个无符号数比较,结果不回送,只影响标志位。
CPL GR,ADR[,XR]
6)算术左移 SLA、算术右移 SRA:把操作数看成带符号数。对寄存器操作数进行移位(GR 的第 0 位——符号位不变。右移时空出的其余位补与第 0 位相同的 1 或 0 ;左移时空出的位补 0 ),位数由有效地址 E 决定。
SLA | ┐ | GR,ADR[,XR] |
SRA | ┘ |
7)逻辑左移 SLL、逻辑右移 SRL:把操作数看成无符号数。对寄存器操作数进行移位(不管左右移,GR 空出的位补 0 ),位数由有效地址 E 决定。
SLL | ┐ | GR,ADR[,XR] |
SRL | ┘ |
8)取有效地址指令LEA:E→寄存器
LEA GR,ADR[,XR]
该指令有几个特殊用途:
【例1】LEA GR0,41H 把立即数 41H 送给 GR0
【例2】LEA GR0,0,GR1 把寄存器 GR1 的内容送给 GR0
【例3】LEA GR1,1,GR1 寄存器 GR1 的内容加 1(相当于 8086 汇编中的 INC 指令)
【例4】LEA GR1,-1,GR1 寄存器 GR1 的内容减 1(相当 8086 汇编中的 DEC 指令)
【例5】LEA GR1,N,GR1 寄存器 GR1 的内容加 N(相当于立即数加法)
【例6】LEA GR1,-N,GR1 寄存器 GR1 的内容减 N(相当于立即数减法)
9)无条件转移指令JMP与条件转移指令JPZ(不小于转)、JMI(小于转)、JNZ(不等于转)、JZE(等于转)
JMP | ┐ | |
JPZ | │ | |
JMI | ├ | ADR[,XR] |
JNZ | │ | |
JZE | ┘ |
●当地址码中缺 [XR] 时,所有转移指令为直接转移( ADR 即为转移地址)
当地址码中有 [XR] 时,所有转移指令为间接相对接转移
●JPZ 与 JMI 根据符号位 FR0 作为判断条件
JNZ 与 JZE 根据零位位 FR1 作为判断条件
10)进栈 PUSH 与出栈 POP:
(1)进栈指令 PUSH:
PUSH | ADR[,XR] |
(SP)-1→SP,E→(SP)
(2)出栈指令 POP:
POP | GR |
((SP))→GR,(SP)+1→SP
注意:出栈指令的目的寄存器是 GR0~GR4,而进栈指令的源操作数不包括 GR0。
11)子程序调用CALL与返回 RET指令
(1)子程序调用指令 CALL:
CALL | ADR[,XR] |
(SP)-1→SP,(PC)+2→(SP),E→(PC)
(2)子程序返回指令 RET:
RET
((SP))→PC,(SP)+1→SP
4、伪指令
1)START:程序开始
2)END:程序结尾
3)常量定义指令 DC:
此伪指令与其它汇编语言中的 DB 指令似。利用 DC 指令可定义:
(1)定义十进制常数:
十进制常数名 | DC | n |
其中 -32768 < n ≤65535
(2)定义字符串常数:
字符串常数名 | DC | '字符串' |
(3)定义十六进制常数:
十六进制常数名 | DC | #h |
其中 0000 ≤ h ≤FFFF
(4)定义地址:
地址常数 | DC | LABLE |
其中 LABLE 是程序中的地址标号
因为 Casl 没有立即数运算指令,所以需要与立即数进行算术逻辑运算时,都要将立即数定义为内存常数进行运算。
4)变量定义指令 DS:用来保留指定字数的存储区域
[LABLE] | DS | n |
其中 n 是十进制常数(≥0),当 n=0 时,存储区域不存在,但标号 LABLE 仍然有效,即代表下一字的地址。
5)宏指令:IN、OUT、EXIT
Casl 中有进行输入、输出及结束程序等宏指令,而没有定义输入、输出符号指令,这类处理由操作系统完成。
程序中出现宏指令时,Casl 生成调用操作系统的指令串,但是,生成的指令串字数不定。
执行宏指令时,GR 的内容保持不变,而 FR 的内容不确定。
(1)输入宏指令 IN:
[LABLE] | IN | ALABLE,NLABLE |
宏指令 IN 从输入装置上输入一个记录,纪录中的信息(字符)依次按字符数据的形式存放在标号为 ALABLE 开始的区域内,已输入的字符个数以二进制数形式存放在标号为 NLABLE 的字中,纪录之间的分隔符号不输入。
(2)输出宏指令 OUT:
[ LABLE] | OUT | ALABLE,NLABLE |
宏指令 OUT 将存放在标号为 ALABLE 开始的区域中的字符数据作为一个记录向输出装置输出,输出的字符个数由标号为 NLABLE 的字的内容指定。输出时,若要纪录间的分隔符号,由操作系统自动插入输出。
(3)宏指令 EXIT :
[LABLE] | EXIT |
宏指令 EXIT 表示程序执行的终止,控制返回操作系统。
二、基本程序结构
1、顺序程序(略)
2、分枝程序
1)简单分支程序
将比较指令(或其它能使标志位发生变化的指令)和条件转移指令结合,可实现分支程序。简单分支程序有两种形式:
在汇编语言中,采用图a的结构容易出错,例如把GB0中的十六进制数转换成 ASCII 码可分成两段, 0~9 的 ASCII 码是 30H~39H ,即加 30H ;A~F 的ASCII 码是 41H~45H ,即加 37 H。两种结构的程序如下:
图a结构 |
图b结构 |
|||||
CPL | GR0,C10 | CPL | GR0,C10 | |||
JMI | L1 | JMI | L1 | |||
ADD | GR0,C37 | ADD | GR0,C7 | |||
JMP | L2 | L1 | ADD | GR0,C30 | ||
L1 | ADD | GR0,C30 | … | |||
L2 | … | C10 | DC | 10 | ||
C10 | DC | 10 | C7 | DC | 7 | |
C37 | DC | #37 | C30 | DC | #30 | |
C30 | DC | #30 |
若采用图a的结构,很容易把JMP L2漏掉,变成A~F 的ASCII 码加了67H。
2)多岔分支程序
可以用多条比较指令和条件转移指令实现多岔分支。
但用散转表实现多岔分支则程序更为简练,其思路是在散转表中存放各个分支程序的入口地址,用查表方法将入口地址放入变址寄存器,然后用JMP指令或CALL指令的间接寻址方式使程序转到此入口。
【例1】根据键盘输入命令转入各命令处理程序
SB | START | ||
AGAIN | IN |
ALABLE,NLABLE |
;输入一个字符串 |
LD | GR1,NLABLE | ;字符串长度 | |
LEA | GR1,0,GR1 | ||
JZE | AGAIN | ;若字符串长度 =0,重输 | |
LD | GR1,ALABLE | ;将第一个字符放到GR1中 | |
LEA | GR1,-65,GR1 | ;减去"A"的ASCII码 | |
JMI | AGAIN | ;若该字符<"A",重输 | |
CPA | GR1,C4 | ||
JPZ | AGAIN | ;若该字符>"D",重输 | |
LD | GR2,ENTRY,GR1 | ;将散转表中的一项地址放入GR2 | |
CALL | 0,GR2 | ;转入地址所指子程序 | |
JMP | AGAIN | ||
ALABLE | DS | 5 | ;输入字符串缓冲区 |
NLABLE | DS | 1 | ;输入字符串长度 |
C4 | DC | 4 | ;常数 5 |
ENTRY | DC | ASUBR | ;子程序A 入口地址 |
DC | BSUBR | ;子程序B 入口地址 | |
DC | CSUBR | ;子程序C 入口地址 | |
DC | DSUBR | ;子程序D 入口地址 | |
END |
在散转表中也可存放转移到各个分支程序入口的转移指令,然后用变址寻址方式的JMP指令或CALL指令使程序转到此入口。
【例2】
SB | START | ||
AGAIN | IN |
ALABLE,NLABLE |
;输入一个字符串 |
LD | GR1,NLABLE | ;字符串长度 | |
LEA | GR1,0,GR1 | ||
JZE | AGAIN | ;若字符串长度 =0,重输 | |
LD | GR1,ALABLE | ;将第一个字符放到GR1中 | |
LEA | GR1,-65,GR1 | ;减去"A"的ASCII码 | |
JMI | AGAIN | ;若该字符<"A",重输 | |
CPA | GR1,C4 | ||
JPZ | AGAIN | ;若该字符>"D",重输 | |
SLL | GR1,1 | ;散转表每项占2 字 | |
JMP | ENTRY,GR1 | ||
ALABLE | DS | 5 | |
NLABLE | DS | 1 | |
C4 | DC | 4 | |
ENTRY | JMP | ASUBR | |
JMP | BSUBR | ||
JMP | CSUBR | ||
JMP | DSUBR | ||
END |
3、循环程序
循环程序也是用转移指令实现,循环的退出一般用循环计数器实现,也可用其它条件来控制退出。
1)用循环计数器控制
【例1】将SD处的100个数搬到TD处(搬家程序)
SB | START | ||
LEA | GR1,100 | ;循环计数器 | |
LEA | GR2,0 | ;偏移地址指针 | |
LOOP | LD | GR0,SD,GR2 | ;源操作数→GR0 |
ST | GR0,TD,GR2 | ;GR0→目的操作数 | |
LEA | GR2,1,GR2 | ;修正偏移地址指针 | |
LEA | GR1,-1,GR1 | ;搬完否? | |
JNZ | LOOP | ||
EXIT | |||
SD | DS | 100 | |
TD | DS | 100 | |
END |
当源操作数和目的操作数有重迭时,若源操作数在目的操作数的前面,则必须采用从下往上的搬法,即先搬最后一个,再一个一个往前搬。
【例2】将 SD 处的 100 个数搬到 TD 处(有重迭)
SB | START | ||
LEA | GR1,100 | ;循环计数器 | |
LEA | GR2,99 | ;偏移地址指针 | |
LOOP | LD | GR0,SD,GR2 | ;源操作数→GR0 |
ST | GR0,TD,GR2 | ;GR0→目的操作数 | |
LEA | GR2,-1,GR2 | ;修正偏移地址指针 | |
LEA | GR1,-1,GR1 | ;搬完否? | |
JNZ | LOOP | ||
EXIT | |||
SD | DS | 1 | |
TD | DS | 100 | |
END |
2)循环次数可能为 0 的循环程序
前面的例子无法实现循环次数可能为0的循环程序(如用连加实现两个数相乘的算法),因为循环计数器预置为0时,实际的循环次数是65536。
【例2】将 A 和 B 两个整数相乘和放到 C 中(不考虑溢出)
SB | START | ||
LD | GR1,B | ;乘数作循环计数器 | |
LEA | GR0,0 | ;乘积清0 | |
LOOP | LEA | GR1,0,GR1 | |
JZE | L1 | ;若乘数为0,退出循环 | |
ADD | GR0,A | ;被乘数加到乘积中 | |
LEA | GR1,-1,GR1 | ;加完否? | |
JMP | LOOP | ||
L1 | ST | GR0,C | ;乘积→C |
EXIT | |||
A | DS | 1 | |
B | DS | 1 | |
C | DS | 1 | |
END |
3)循环次数不定的循环程序
不用循环计数器,而用其它方法控制循环的退出。
【例4】测试字符串STR的长度,并将它保存到L中
SB | START | ||
LEA | GR1,0 | ;字符串长度计数器(兼地址指针)清0 | |
LOOP | LD | GR0,STR,GR1 | ;取一字符 |
CPL | GR0,FEND | ||
JZE | L1 | ;若是结束符,退出循环 | |
LEA | GR1,-1,GR1 | ;长度计数器+1 | |
JMP | LOOP | ||
L1 | ST | GR0,L | ;长度→L |
EXIT | |||
L | DS | 1 | |
FEND | DC | '$' | |
STR | DC | 'This is a sample$' | |
END |
4)多重循环
【例5】冒泡排序程序。
一组有符号数al(i=1,2,…,100),存放在Al开始的连续单元中。下面一程序将这组数在原来的内存区中由大到小重新排序。
SB | START | ||
LEA | GR1,99 | ||
ST | GR1,CN | ;外循环计数器初值 | |
L1 | LEA | GR1,0 | ;外循环指针 |
LEA | CR2,1 | ;内循环指针兼计数器初值 | |
LEA | GR3,0 | ;交换标志置0 | |
L2 | LD | GR0,A1,GR1 | |
CPA | GR0,A1,GR2 | ||
JPZ | L3 | ;内循环体,若Ai≥Ai+1,不动 | |
LD | GR4,A1,GR2 | ;若Ai<Ai+1,交换 | |
ST | GR4,A1,GR1 | ||
ST | GR0,A1,GR2 | ||
LEA | GR3,1 | ;交换标志置1 | |
L3 | LEA | GR1,1,GR1 | ;内循环调整 |
LEA | GR2,1,GR2 | ||
CPL | GR2,CN | ||
JZE | L2 | ;内循环控制 | |
JMI | L2 | ||
LEA | CR3,0,CR3 | ||
JZE | L4 | ;本次内循环交换标志为0,排序结束 | |
LD | GR4,CN | ;外循环计数减1 | |
LEA | GR4,-1,GR4 | ||
JZE | L4 | ;排序结束 | |
ST | GR4,CN | ||
JMP | L1 | ||
L4 | EXIT | ||
A1 | DS | 100 | |
CN | DS | 1 | |
END |
这是一个二重循环结构的程序。程序中采用冒泡排序的方法,首先从第1个单元开始,依次把相邻两个元素比较,即第1个单元内的数与第2个单元内的数比较,第2个单元内的数与第3个单元内的数比较,…第99个单元与第100个单元的数相比较。每次把较大者放在前面单元,较小者放在后面单元。这样第一次遍历做了n-1次比较,最后最小的元素放在第100个单元。
第二次遍历再从头开始,依次将相邻两个元素进行比较,把n-1个元素遍历一次需做n-2次比较,这样,第99个单元存放全部元素中次小元素。
如此反复,小的元素往下,大的元素向上犹如汽泡上浮,因此这种排序的方法给它一个形象的名字,叫做冒泡算法。在程序中还设GR3为交换标志,若本次内循环结束交换标志为0,说明没有交换。即所有的Ai≥Ai+1,排序结束。
4、子程序
所谓子程序是指完成一定功能的指令序列,在主程序的若干地方可以用CALL指令对它调用,子程序结束时通过RET指令返回主程序。在子程序中,如果没有特别说明,则子程序中所要使用到的寄存器要保护。在子程序的开始用PUSH指令将子程序中要使用的寄存器保护,在返回前用POP指令恢复。主程序与子程序可以处在同一个程序中,此时,主程序对子程序的调用称为程序内的调用,CASL中也允许主程序与子程序不在同一个程序中,这样的调用就称为程序间凋用。
在子程序中一般不将GR4作为通用寄存器使用,因为GR4作为栈顶指针。如果其它通用寄存器不够用,要使用GR4,则先将GR4中的栈顶指针值保存,然后使用GR4,GR4使用完后,立即恢复栈指针的值。
1)参数传递
主程序调用子程序时,通常要向子程序传递参数(入口参数)。子程序结束,也会有数据送回主程序(返回参数)。当参数较少时,主程序将参数存于寄存器(GR0~GR3)传递给子程序;也可放于内存给子程序;当传递参数较多时,通常存于内存(参数表),并将参数表的首地址存于寄存器传递给子程序;也可以通过堆栈传递参数值。
【例1】寄存器传递参数
MAIN | START | ||
LD | GR0,A | ;被加数送GR0 | |
LEA | GR1,B | ;加数送GR1 | |
CALL | SUBA | ||
ST | GR0,C | ;结果回送 | |
…… | |||
EXIT | |||
SUBA | ADD | GR0,0,GR1 | |
RET | |||
A | DC | 56 | |
B | DC | 89 | |
C | DS | 1 | |
D | DC | 186 | |
E | DC | 567 | |
F | DS | 1 | |
END |
【例2】内存传递参数
MAIN | START | ||
LD | GR0,A | ;被加数送GR0 | |
LD | GR1,B | ||
ST | GR1,BUF | ;加数送BUF内存单元 | |
CALL | SUBA | ||
ST | GR0,C | ;结果回送 | |
…… | |||
EXIT | |||
SUBA | ADD | GR0,BUF | |
RET | |||
BUF | DS | 1 | |
A | DC | 56 | |
B | DC | 89 | |
C | DS | 1 | |
D | DC | 186 | |
E | DC | 567 | |
F | DS | 1 | |
END |
【例3】参数表传递参
MAIN | START | ||
LEA | GR1,LIST | ;参数表首址送GR1 | |
CALL | SUBA | ||
EXIT | |||
SUBA | LD | GR0,0,GR1 | ;被加数 |
ADD | GR0,1,GR1 | ;与加数相加 | |
ST | GR0,2,GR1 | ;结果回送 | |
RET | |||
LIST | DC | 56 | |
DC | 89 | ||
DS | 1 | ||
END |
【例4】堆栈传递参数
MAIN | START | ||
LD | GR1,A | ||
PUSH | 0,GR1 | ;被加数压入堆栈 | |
LD | GR1,B | ||
PUSH | 0,GR1 | ;加数压入堆栈 | |
LEA | GR1,C | ||
PUSH | 0,GR1 | ;结果地址压入堆栈 | |
CALL | SUBA | ||
LEA | GR4,3,GR4 | ;堆栈指针退回 | |
EXIT | |||
SUBA | LD | GR0,3,GR4 | ;被加数 |
ADD | GR0,2,GR4 | ;与加数相加 | |
LD | GR1,1,GR4 | ;结果地址 | |
ST | GR0,0,GR1 | ;结果回送 | |
RET | |||
A | DC | 56 | |
B | DC | 89 | |
C | DS | 1 | |
END |
2)子程序的返回
子程序通过RET指令返回主程序。RET指令的功能是将栈顶的返回地址置入PC。一般情况,应该保证返回时的堆栈指针SP(GR4)与调用时一致。以保证正确返回。但CASL中,GR4兼作通用寄存器,变址器和栈顶指针,因此在子程序中可以通过GR4修改返回地址,这样子程序返回时,就不一定返回到主程序的调用点下面的一条指令。在早期的程序员CASL试题中常常利用这一技巧。
3)现场保护
通常情况下,子程序都应该保护它所使用过的寄存器。因PUSH指令不能保护GR0,所以GR0用ST指令保护,用LD指令恢复。
【例4】无符号乘法。
有两个无符号数N1,N2,下面一子程序实现N1×N2返回。调用子程序时GR1存放参数N1、N2和相乘结果的存放区域的首地址,如下图所示。设相乘过程不会产生溢出。
GR1+0 | N1 |
+1 | N2 |
+2 | 结果 |
MULT | START | ||
ST | GR0,SAVE | ||
PUSH | 0,GR2 | ;保护寄存器 | |
PUSH | 0,GR3 | ||
LEA | GR2,0 | ;部分积清零 | |
LEA | GR3,16 | ;循环计算器 | |
LD | GR0,1,GR1 | ;取出乘数N2 | |
LOOP | SLL | GR2,1 | ;部分积左移一位 |
SLL | GR0,0 | ;测乘数N2最高位是否为1 | |
JPZ | L1 | ;若为0,跳过这一位 | |
ADD | GR2,0,GR1 | ;若为1,部分积加上被乘数 | |
L1 | SLL | GR0,1 | ;乘数左移一位,算下一位 |
LEA | GR3,-1,GR3 | ;16位算完否? | |
JNZ | LOOP | ||
ST | GR2,2,GR1 | ;存放乘积 | |
LD | GR0,SAVE | ;恢复寄存器的值 | |
POP | GR3 | ||
POP | GR2 | ||
RET | |||
SAVE | DS | 1 | |
END |
象手算乘法一样,由于两个数是二进制数,所以唯一的问题是乘0还是乘1;显然,乘0结果为0,而乘1则结果与开始的数(被乘数)相同。因此在二进制乘法中每一步都可以简化成下列运算:
如果乘数中现有位为1,则将被乘数加到部分积上,为0则不加。剩下的问题是部分积和被乘数每次相加要进行移位,保证每次相加都正确对准。乘法过程可分成以下几步:
(1)积清0,设置计数器初值16。
(2)积左移1位。
(3)乘数最高位是否为1。
(4)若为1,则被乘数加积。
(5)乘数左移1位,计数器减1,若不为0转(2)。
程序中GR0存乘数,GR2存积,GR3为计数器
二、汇编语言常用子程序
1、拆字与拼字:
【例1】将 GR0 中的四位 BCD 码从高到低依次存放到 GR2 所指的四个内存单元中。
START | |||
LEA | GR3,4 | ;循环计数器 | |
L1 | ST | GR0,REG | ;保护其余几位 BCD 码 |
AND | GR0,C000F | ;屏蔽高 3 位,留下最低 1 位 BCD 码 | |
ST | GR0,3,GR2 | ;将此位 BCD 码存放到 GR2 所指第四个内存单元 | |
LD | GR0,REG | ;恢复其余几位 BCD 码 | |
SRL | GR0,4 | ;将已处理过的此位 BCD 码移出 | |
LEA | GR2.-1,GR2 | ;地址指针减 1 | |
LEA | GR3,-1,GR3 | ;循环计数器减 1 | |
JNZ | L1 | ;未处理完,继续 | |
RET | |||
C000F | DC | #000F | ;十六进制常数,屏蔽高 3 位 BCD 码用 |
REG | DS | 1 | ;暂存单元 |
END |
【例2】将存放在 GR2 所指的四个内存单元中的四位 BCD 码依从高到低顺序压缩到 GR0 中 。
START | |||
LEA | GR0,0 | ;GR0 清 0 | |
LEA | GR3,4 | ;循环计数器 | |
L1 | SLL | GR0,4 | ;将已处理过的 BCD 码移到高位 |
LD | GR1,0,GR2 | ;GR1 用作临时工作寄存器 | |
AND | GR1,C000F | ;屏蔽高 12 位 | |
ST | GR1,0,GR2 | ;对内存单元中的 BCD 码预处理 | |
ADD | GR0,0,GR2 | ;将已处理过的此位 BCD 码加到 GR0 低位 | |
LEA | GR2.1,GR2 | ;地址指针指向下一位 BCD 码 | |
LEA | GR3,-1,GR3 | ;循环计数器减 1 | |
JNZ | L1 | ;未处理完,继续 | |
RET | |||
C000F | DC | #000F | ;十六进制常数,屏蔽高 12 位二进制数 |
END |
2、数字与 ASCII 码之间的相互转换:
十进制数字 0~9 的 ASCII 码是 30H~39H ,所以只要将十进制数(BCD 码)加 30H 就是对应的 ASCII 码。
十六进制数转换成 ASCII 码可分成两段, 0~9 的 ASCII 码是 30H~39H ,即加 30H ;A~F 的ASCII 码是 41H~45H ,即加 37 H。
【例1】将 GR0 中的四位 BCD 码化成 ASCII 码从高到低依次存放到字符串变量 STR 中。
START | |||
LEA | GR2,3 | ;相对于 STR 首址的地址指针 | |
LEA | GR3,4 | ;循环计数器 | |
L1 | ST | GR0,REG | ;保护其余几位 BCD 码 |
AND | GR0,C000F | ;屏蔽高 3 位,留下最低 1 位 BCD 码 | |
ADD | GR0,C30 | ;转换成 ASCII 码 | |
ST | GR0,STR,GR2 | ;将 ASCII 码存放到 GR2 所指第四个内存单元 | |
LD | GR0,REG | ;恢复其余几位 BCD 码 | |
SRL | GR0,4 | ;将已处理过的此位 BCD 码移出 | |
LEA | GR2.-1,GR2 | ;地址指针减 1 | |
LEA | GR3,-1,GR3 | ;循环计数器减 1 | |
JNZ | L1 | ;未处理完,继续 | |
RET | |||
C000F | DC | #000F | ;十六进制常数,屏蔽高 3 位 BCD 码用 |
C30 | DC | #30 | ;十六进制常数 30 |
STR | DS | 4 | |
REG | DS | 1 | ;暂存单元 |
END |
【例2】将 GR0 中的 16 位二进制数化成四位十六进制数 ASCII 码从高到低依次存放到字符串变量 STR 中。
START | |||
LEA | GR2,3 | ;相对于 STR 首址的地址指针 | |
LEA | GR3,4 | ;循环计数器 | |
L1 | ST | GR0,REG | ;保护其余几位二进制数 |
AND | GR0,C000F | ;屏蔽高 12 位,留下最低 4 位二进制数 | |
CPL | GR0,C10 | ;< 10 否? | |
JMI | L2 | ;< 10 跳过加 7 ,只加 30H | |
ADD | GR0,C7 | ;≥ 10,加 30H 前先加上 7 | |
L2 | ADD | GR0,C30 | ;加上 30H |
ST | GR0,STR,GR2 | ;将 ASCII 码存放到 GR2 所指第四个内存单元 | |
LD | GR0,REG | ;恢复其余几位二进制数 | |
SRL | GR0,4 | ;将已处理过的此 4 位二进制数移出 | |
LEA | GR2.-1,GR2 | ;地址指针减 1 | |
LEA | GR3,-1,GR3 | ;循环计数器减 1 | |
JNZ | L1 | ;未处理完,继续 | |
RET | |||
C000F | DC | #000F | ;十六进制常数,屏蔽屏蔽高 12 位二进制数 |
C30 | DC | #30 | ;十六进制常数 30 |
C10 | DC | 10 | ;十进制常数 10 |
C7 | DC | 7 | ;常数 7 |
STR | DS | 4 | |
REG | DS | 1 | ;暂存单元 |
END |
【例3】将字符串 STR 中的四位十六进制数的 ASCII 码化成 16 位二进制数放到 GR0 中 。
START | |||
LEA | GR0,0 | ;GR0 清 0 | |
LEA | GR2,0 | ;相对于 STR 首址的地址指针 | |
LEA | GR3,4 | ;循环计数器 | |
L1 | SLL | GR0,4 | ;将已处理过的十六进制数移到高位 |
LD | GR1,STR,GR2 | ;GR1 用作临时工作寄存器 | |
AND | GR1,C00FF | ;屏蔽高 8 位 | |
SUB | GR1,C30 | ;减去30H | |
CPL | GR1,C0A | ;< 10 否? | |
JMI | L2 | ;< 10 ,完成转换 | |
SUB | GR1,C7 | ;≥ 10,再减去 7 | |
L2 | ST | GR1,STR,GR2 | ;将 STR 中的 ASCII 码转换成十六进制数 |
ADD | GR0,STR,GR2 | ;将此位十六进制数加到 GR0 低位 | |
LEA | GR2.1,GR2 | ;地址指针指向下一位 ASCII 码 | |
LEA | GR3,-1,GR3 | ;循环计数器减 1 | |
JNZ | L1 | ;未处理完,继续 | |
RET | |||
C00FF | DC | #00FF | ;十六进制常数,屏蔽高 8 位用 |
C30 | DC | #30 | ;十六进制常数 30 |
C0A | DC | #0A | ;十六进制常数 0A |
C7 | DC | 7 | ;常数 7 |
STR | DS | 4 | |
END |
3、利用加减法及移位指令做乘法:
1)左移指令可将操作数乘 2 的整数次方(2、4、8、16);右移指令可将操作数除以 2 的整数次方。
若操作数是无符号数,用逻辑移位指令;若操作数是有符号数,用算术移位指令。
【例1】将 GR0 中的二进制数乘以 8。
SLL | GR0,3 |
【例2】将 GR0 中的带符号二进制数除以 4。
SRA | GR0,2 |
2)将移位指令和加减法结合起来可完成乘数不是 2 的整数次方的乘法运算。
【例1】将 GR0 中的二进制数乘以 10。
START | ||
SLL | GR0,1 | |
ST | GR0,REG | |
SLL | GR0,2 | |
ADD | GR0,REG | |
RET | ||
REG | DS | 1 |
END |
【例2】将 GR0 中的二进制数乘以 7。
START | ||
ST | GR0,REG | |
SLL | GR0,3 | |
SUB | GR0,REG | |
RET | ||
REG | DS | 1 |
END |
4、二进制数与十进制数的转换
1)二化十:
将二进制数转换为十进制数的一种常用算法是将被转换的二进制数依次被 10i( 对 16 位二进制数,i为 4、3、2、1、0)除,所得的商即为该十进制数位的值,其余数再被下一个 10i 除。一般用减法代替除法,即一边减 10i,一边计数器加 1,直到不够减再进行下一位 10i-1。以求得十进制数的各位数。
例如:一个相当于十进制数 34635 的二进制数,可先用 10000 去减,可减 3 次,得万位数是 3;再用 1000 去减,得千位数是 4;……
【例1】将 GR0 中的二进制数转换为十进制数的ASCII 码放入字符串 STR 中。
START | |||
LEA | GR1,0 | ;减数表及字符串指针 | |
LEA | GR2,5 | ;循环计数器 | |
L1 | LEA | GR3,48 | ;该十进制位的数码预置 0 的 ASCII 码 |
L2 | LEA | GR3,1,GR3 | ;数码位的 ASCII 码加 1 |
SUB | GR0,SNO,GR1 | ;操作数减去 10i | |
JPZ | L2 | ;够减,继续 | |
ADD | GR0,SNO,GR1 | ;不够减,操作数及数码位的 ASCII 码恢复 | |
LEA | GR3,-1,GR3 | ||
ST | GR3,STR,GR1 | ;转换好的该位 ASCII 码存入结果字符串 | |
LEA | GR1,1,GR1 | ;地址指针加 1 | |
LEA | GR2,-1,GR2 | ;循环计数器减 1 | |
JNZ | L1 | ;未结束,继续下一位 | |
RET | |||
SNO | DC | 10000 | |
DC | 1000 | ||
DC | 100 | ||
DC | 10 | ||
DC | 1 | ||
STR | DS | 5 | ;转换结果字符串 |
END |
1)十化二:
将十进制数转换为二进制数的算法基础是下面公式:
N = (Dn-1*10n-1+Dn-2*10n-2+……+D1*101+D0*100
= ((…((Dn-1*10+Dn-2)*10+……+D1)*10+D0)*10
可以用循环程序实现此公式,*10 可用移位及加法指令完成。
【例2】将存放在字符串 STR 中的五位十进制数(<65536)的 ASCII 码转换成二进制数放到 GR0 中 。
START | |||
LEA | GR0,0 | ;转换结果寄存器清 0 | |
LEA | GR2,5 | ;循环计数器 | |
LEA | GR1,0 | ;地址指针(偏移量) | |
L1 | SLL | GR0,1 | ;转换结果*10,先乘以 2 |
ST | GR0,REG | ;暂存 2*X | |
SLL | GR0,2 | ;2*X*4=8*X | |
ADD | GR0,REG | ;8*X + 2*X | |
LD | GR3,STR,GR1 | ;取一位 ASCII 码 | |
AND | GR3,C000F | ;将 ASCII 码变成 BCD 码 | |
ST | GR3,REG | ;结果暂存 | |
ADD | GR0,REG | ;将新的一位 BCD 码加到转换结果中 | |
LEA | GR1,1,GR1 | ;地址指针加 1 | |
LEA | GR2,-1,GR2 | ;循环计数器减 1 | |
JNZ | L1 | ;未结束,继续下一位 | |
RET | |||
C000F | DC | #000F | ;十六进制常数,屏蔽高 12 位二进制数 |
STR | DC | '35475' | |
REG | DS | 1 | ;暂存单元 |
END |
5、求累加和
【例1】将变量 NUMBER 中的 5 个二进制数累加后放入变量 SUM 中。
START | |||
LEA | GR2,NUMBER | ;地址指针 | |
LEA | GR3,5 | ;循环计数器 | |
LEA | GR0,0 | ;累加和清0 | |
L1 | ADD | GR0,0,GR2 | ;累加 |
LEA | GR2,1,GR2 | ;地址指针指向下一个二进制数 | |
LEA | GR3,-1,GR3 | ;计数器减1 | |
JNZ | L1 | ;未完,继续 | |
ST | GR0,SUM | ;累加结束,累加和送入 SUM 单元 | |
RET | |||
NUMBER | DS | 5 | |
SUM | DS | 1 | |
END |
【例2】将自然数1~16累加后放入变量 SUM 中。
START | |||
LEA | GR1,0 | ;地址指针 | |
LEA | GR0,0 | ;累加和清 0 | |
L1 | LEA | GR1,1,GR1 | ;自然数加1 |
ST | GR1,SUM | ;加数(自然数)送入内存暂存 | |
ADD | GR0,SUM | ;累加 | |
CPA | GR1,A | ||
JNZ | L1 | ;未完,继续 | |
ST | GR0,SUM | ;累加结束,累加和送入 SUM 单元 | |
EXIT | |||
A | DC | 16 | |
SUM | DS | 1 | |
END |
LEA GR1,0
LEA GR0,0 ;累加和(被加数)
CF LEA GR1,1,GR1
;
ADD GR0,C
JNZ CF
ST GR0,B
EXIT
A DC 16
6、利用递归方法求5的阶乘
START | ;主程序 | ||
LEA | GR1,5 | ;入口参数5(阶乘)→GR1 | |
CALL | FACT | ;调用FACT子程序 | |
EXIT | |||
FACT | LEA | GR1,0,GR1 | |
JNZ | IIA | ;GR1未到0,继续递归 | |
LEA | GR1,1 | ;GR1=0,退出递归 | |
RET | |||
IIA | PUSH | 0,GR1 | ;保护这一级乘数 |
LEA | GR1,-1,GR1 | ;乘数减1 | |
CALL | FACT | ;继续调用FACT子程序自己 | |
POP | GR1 | ;逐级退出递归,恢复本级乘数 | |
LEA | GR0,0 | ||
CALL | MUL | ;乘上本级乘数 | |
RET | |||
MUL | ADD | GR0,RESUL | |
LEA | GR1,-1,GR1 | ||
JNZ | MUL | ||
ST | GR0,RESUL | ||
RET | |||
RESUL | DC | 1 | |
END |
转帖于:软件水平考试_考试吧
- 推荐给朋友
- 收藏此页
·软件工程习题200题之一 (2004-12-29 23:53:00)
·Casl汇编语言辅导(下) (2004-12-29 23:52:00)