MITS6004第二课笔记

这节课讲述的内容主要是汇编语言和risc-v。
所以已经开始真正设计到正式的教学内容了。

汇编语言

我们所使用的各种高级语言需要靠机器语言即汇编语言最终来是实现软件与硬件的交互。

微处理器的组成

机器语言直接的反映了这个结构,基本上机器语言就是操作寄存器读取计算,寄存器和内存交换数据等等。

寄存器文件

寄存器文件可以理解位一个存储空间,它不是很大,但是非常的快,我们用它来存储频繁需要的数据。

算数逻辑单元

算数逻辑单元是用来做所有的计算的,例如加减法。通常算数单元从寄存器接受两个源操作数,计算结果

继续存到寄存器中。

主内存

主内存比较慢,但是很大,当我们需要使用主内存时,将内容读取到寄存器中然后对其进行计算。寄存器与内存之间存在接口。 主内存还存放程序以及数据。

微处理器架构

  • 首先需要确定寄存器的大小,每个寄存器的大小都是固定的,假定为32bits。
  • 寄存器的数量比较小,比如有32个。
  • 算数逻辑单元直接进行寄存器数据之间的运算,可以从两个寄存器间读取,然后进行计算。
  • 内存很大,可以用G来衡量,用来承载程序和数据。
  • 数据可以在寄存器和内存之间来回传输。使用load或者storage。

汇编语言程序

  • 汇编语言程序是由一些非常基本的指令组成的指令序列,顺序执行除非被控制流指令改变。
  • 每一条指令都出自于如下的几种运作
    • 算数逻辑运算
    • 加载
    • 存储
    • 控制传输指令

数组求和

这边解释了汇编语言进行数组求和的原理,base是数组的地址也就是4.

从加载初始函数到循环,汇编语言中可以实现循环。add函数大概是一个变量和另一个变量相加然后存到变量里。

高级语言与机器语言的区别

高级语言:

初级算数与逻辑计算

复杂的数据类型以及数据结构

复杂的控制机制例如循环等

不能直接交给硬件运行

汇编语言:

初级算数和逻辑计算

初级数据结构,bits和整数

控制分支或者跳转

可以直接在硬件上运行

指令集架构

讲述了ISA是联系软件和硬件的东西。决定了能够进行的计算和存储位置,精确的描述了程序如何使用他们。

这节课将使用risc-v32位处理器。

RISC-V

这一段讲了risc-v架构的具体情况。32位版本的

32个通用寄存器,每个都是32bit大小从0开始,寄存器的x0存储着0且不可修改。

每个内存地址也是32位宽的。存储指令和数据,每次我们读取数据都是一次访问32位,指令编码都必须32位。

一个字节(byte)是八位。四个字节等于一个字(word)。

  • ①:1字节(byte) = 8位(bit)
  • ②:在16位的系统中(比如8086微机) 1字 (word)= 2字节(byte)= 16(bit)
  • ​ 在32位的系统中(比如win32) 1字(word)= 4字节(byte)=32(bit)
  • ​ 在64位的系统中(比如win64)1字(word)= 8字节(byte)=64(bit)

因为我们的字节是32位宽的,所以可以寻址2到32字节

三种指令

三种指令。

第一种是计算指令,通过ALU实现,可以进行数学而计算和逻辑计算。

第二种是加载和存储,可以在寄存器和内存之间传递数据。

第三种是控制流,可以跳转指令,而不是执行下一条。

计算指令

有四种不同类型的ALU运算。

算数运算,比较运算,逻辑和移位运算。

大致有两种不同形式的指令,最基本的是寄存器到寄存器指令,从两个不同或者相同的寄存器获取源操作数,然后存到一个指定的寄存器中(可以相同可以另外)。

add,sub 加减。

slt, sltu 代表set less than和set less than unsigned。

and,or,xor

sll,srl,sra代表 shift left logical,shift right logical,shift right arithmetic。

基本格式:指令 目标寄存器 源寄存器1 源寄存器2

第二种形式

寄存器数值与常量计算

addi,i的意思是immediate,与其他形式不同的是将寄存器换成了常量。

一个问题是为什么缺少了减法,因为可以直接加上负数。

二进制加法和左位移示例

稍微解释一下,就是把x1向做移动x2个比特然后把得到的结果存入x3。注意点是,宽度固定,多余的比特会被直接丢弃。

复杂计算

首先b加上3,然后把结果右移c位,然后再减1。

在汇编中不能直接写一个式子,需要将其拆分成基本计算。

我我么的指令可以从两个指定的源操作数,和一个目标操作数,也就是三个地址指令。

假设a,b,c是寄存器中的x1,x2,x3。我们使用x4和x5作为临时变量分别为t0和t1、

控制流指令

在高级语言中有if等来控制语言执行,在汇编语言中,我们使用比较来控制代码跳转。

需要条件控制指令。

branch if equal,branch if not equal,branch if less than,branch if greater than or equalto ,branch less than unsigned,branch greater than or equal to unsigned。

这里提出了一个问题,为什么没有不大于,原因是可以直接反转结果,没有必要重新定义。

提到了为什么是bge,也就是大于等于,是因为如果a大于等于b,就直接else了。

然后又提到了为什么有beq这一段,对比x0和x0,这一段我也蒙了,但是仔细回想一下是因为如果没有跳转的话汇编语言是逐句执行的,所以要跳过else直接到end。

无条件跳转

不需要像上面一样写一句beq x0 x0,我们有无条件跳转指令。

jal 是jump and link

这边讲了两个无条件跳转,jalr是长距离跳转,因为我们的寄存器大小有限,所以长距离跳转通过加上常量实现。

label是一个从当前位置的偏移量。我们跳转的长度是与现在位置有关的,

jalr是给了一个寄存器加上一个常量,来作为目标地址。通过寄存器的值来跳转,就可以跳转到我们主内存的任何位置。

怎么样让我们的jal指令可以跳出32bit的限制。

常量以及指令的编程限制

指令要被编写成32bit的长度,一个运算需要10bit,因为我们有32个寄存器所以需要5bit来表示一个寄存器地址,还需要一个目标寄存器地址。所以如果只有一个源寄存器加上一个常数的话,常数最多可以占12位。更大的常数需要被存储在内存中或一整个寄存器,然后单独使用。

回到上面的跳转,正常情况下需要10bits来表示跳转运算,5bits来表示链接,但是实际上跳转运算使用了7bits,加上5,所以还剩下20bits剩下。

通过内存进行计算

我们并不直接使用主内存的地址进行计算,因为主内存的地址长达32bit,刚好占满一个寄存器,所以我们将数据读取到寄存器来进行运算,然后将结果继续存入内存中。

提了一点的就是,读取和存储使用的是16进制的地址,0x表示16进制。

加载存储指令

lw意思是load word,sw意思是store word。(32位我们使用的,不包括其他变体,加载和存储的单位是字)。

读取和加载的地址是寄存器的值加上一个偏移量,也就是base加上offset。

数组相加

首先从x1中读取x10,x10包含着已经读取好的100,也就是主内存中100的base,base是数组的地址也就是4。

其次再读取x10加上偏移量4,也就是主内存中104的值是n,n是数组的长度。

然后将x3清零。相当于初始化。

接着进入一个循环,将第一个数字读取到x4中临时存储,然后将x3和x4相加存入x3,然后将x1的值偏移4,就是下一个数字的地址,然后将n的值减一,这里出现了一个新的运算bnez,我猜测为branch not equal zero。如果n不为0就是数组没有到头,就继续进行村换,最后将x3的值存入x10偏移8的位置,也就是108sum。

伪指令

我们也有一些伪指令来更容易理解的表示代码。

我们也有一些别名的伪指令,来代表实际的指令,这样可以让代码更容易读懂一些。

例如上面的bnez就是使用的伪指令。


MITS6004第二课笔记
https://steammilk.com/2023/02/09/2023-all/6-s004-lec2/
作者
蒸奶泡
发布于
2023年2月9日
更新于
2025年1月8日
许可协议