DLX Lab

Lab1 WinDLX使用及指令流水线冲突性分析

  • 首先载入GCM.SINPUT.S两个汇编程序(GCM.S中需要用到INPUT.S程序代码),它们会被重定向到同一个目标文件中。
  • 通过观察WinDLXCode窗口、Clock Cycle Diagram窗口以及Memory/Symbols菜单栏,可以发现Data段是由0x1000开始的,而且0x1000存放的同时也是Prompt1标号的起始地址。
  • GCM.S中,首先将Prompt1提示符保存到R1寄存器中之后然后jal指令无条件跳转到InputUnsigned标号处(在Text段中,地址为0x144)。这里会出现一次控制冲突,紧跟在jal指令后面的add指令会被清空(注意跳转发生在第二个stage,因此只清空后面紧跟着的一条指令)。

  • 进入InputUnsigned的内部先完成Callee save,将R2R5寄存器保存到SaveR2SaveR5(分别用space.指令以4字节填充地址单元的地址)
  • 无论何时遇到一条trap指令时,DLX处理器的流水线将被清空(即trap一直在IF阶段停顿直到当前流水线中的指令执行完毕)

  • .word指令表示就将后面的内容以字为单位连续存储到内存中。
  • trap 5r14中的字符串重定向到标准输出(i.e.显示提示符)
  • trap 3将标准输入重定向到r14中(i.e.从控制台中读取用户输入的数字,以回车作为结束)
  • 在循环处理前将r1置为0用于保存最后的结果。
  • 循环读取输到ReadBuffer中的数据直到读取到Line Feed回车键,通过seqi指令将r3寄存器与回车键的ASCII值进行比较对r5寄存器进行赋值。然后bnez根据r5寄存器的值来选择是否跳转到Finish标号。再将当前数值减去48(0x30)将ASCII数字字符转换为二进制数。将r1乘10之后再加上从ReadBuffer中读取的数字(目的是为了将控制台中输入的数字转换成二进制数),最后再移动ReadBuffer指针以取下一个字符。最后完成restore回到GCM.S中。
  • 观察可以发现jar跳转到label前会把当前指令的下一条指令的地址放入r31中以便返回。
  • lbu r3, 0x0(r2)seqi r5, r3, 0xa之前存在r3寄存器RAW数据相关导致的数据冲突。因为lbu指令需要访问存储器,最快取出数据也是在MEM阶段末,但在此时seqi指令已经处于第三个阶段并已经经过ALU了。为了解决这种数据冲突,只通过转发是不能解决的(时光不能倒流),还需要通过在两条指令之间插入nop指令,插入之后lbu处于MEM阶段,seqi处于ID阶段,seqi完全可以在进行ALU运算之前使用到通过转发部件(由MEM阶段末转发到ID/EX寄存器)传送的lbu取出的结果,就可以解决数据冲突。

  • seqi r5, r3, 0xabnez r5, Finish之间存在r5寄存器RAW数据相关导致的数据冲突。之前老师上课专门有让大家讨论过Branch放到第二个阶段会带来什么问题。因为seqi指令在EX阶段的时钟周期末尾才更新r5的值,但bnez在ID阶段的时钟周期初就需要R5的值来决定是否要发生跳转(时光不能倒流)。同样也不能只用转发部件来转发,还需要在两条指令之间插个气泡(bnez已经随着seqi因为上一条指令的数据相关停顿过一次了)。此时再从EX阶段末得到的值转发到IF/ID寄存器中。

  • multu r1, r1, r4add r1, r1, r3之间存在r1寄存器RAW的数据相关导致的数据冲突。因为multu指令是完成浮点数乘法,实验开始时就要求将浮点的所有运算设定为4个时钟周期。因此add指令也需要停顿四个时钟周期等待multu浮点运算完成后更新r1寄存器才进行转发(由EX阶段末转发到ID/EX寄存器),这样才能解决数据冲突。

  • subi r3, r3, 0x30add r1, r1, r3之前存在r3寄存器RAW的数据相关导致的数据冲突。直接通过转发(而不是等到WB阶段)就能解决。
  • j Loop指令会导致控制冲突,因此紧跟在jump指令之后的lw指令就被清空了。
  • j r31后会插入一个nop指令到延迟槽中。
  • seq r3, r1, r2bnez r3, Result之间存在r3寄存器RAW的数据相关导致的数据冲突。需要插入一个气泡,再将EX阶段末得到的r3更新后的数据转发到ID/EX寄存器,从而解决控制冲突。同理,sgt r3, r1, r2bnez r3, Result也是类似的。

  • 最后再来分析一下使用转发部件解决冲突和不使用转发部件的性能差异。由图可以看出,使用转发部件后,停顿减少了13次。这13次都是RAW Stall,结合所学知识,其他三类LD指令、分支指令以及浮点指令导致的冲突都是不能单靠转发部件能解决的。同时注意到增加转发部件执行程序的时钟周期也从146降到了129,由CPU性能方程可知,提高了整体的性能。

Lab2 DLX处理器程序设计

  • 浮点数除法DLX汇编代码实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    ;fdiv.s
    .data
    ;data for input
    Scanf0: .asciiz "\nEnter the 1st number:"
    Scanf1: .asciiz "\nEnter the 2nd number:"

    ;data for print
    Printf0:.asciiz "\n(1st number) %g divided by (2nd number) %g = %g"
    Printf1:.asciiz "\n divide by 0!!"
    .align 2
    Printf: .word Printf0
    Printf3: .word Printf1
    PrintfValue1:.space 8
    PrintfValue2:.space 8
    PrintfValue3:.space 8
    .text
    .global main
    main:
    addi r1,r0,Scanf0 ;input dividend
    jal InputUnsigned ;This function convert ASCII to Binary

    add r2,r1,r0 ;r2=dividend
    addi r1,r0,Scanf1 ;input divisor
    jal InputUnsigned

    bnez r1,num2notzero ;(r1!=0) PC=num2notzero. This instruction to guarantee divisor is not zero
    addi r14,r0,Printf3 ;when num2=0 print the prompt message
    trap 5 ;print this message to standard output
    j Result ;jump to the end and interrupt the running program
    num2notzero:
    ;convert interger num1 and num2 to double floating-point
    movi2fp f1,r1 ;Copies one word from integer register r1 to fp register f1.
    movi2fp f2,r2
    cvti2d f0,f1 ;Converts a signed integer to double precision float.
    cvti2d f2,f2
    sd PrintfValue1,f2
    sd PrintfValue2,f0

    divd f8,f2,f0 ;divide for double precision floats
    ;print the quotient
    sd PrintfValue3,f8
    addi r14,r0,Printf ;r14 is specialized for trap instruction
    trap 5
    Result:
    trap 0

  • 程序运行结果图


  • 首先查看DLX指令编码表,通过Chrome浏览器中的vim插件进行模糊查找定位到双精度浮点除法指令divd。因为除法程序需要用到input.s文件将从命令行输入的ASCII字符转换成二进制整型数字(因此先不采用浮点寄存器,最后再进行整型到浮点的转换)。分析InputUnsigned可以发现,它会将转换后的结果放入到R1寄存器中。
  • 注意trap 5需要的r14存放的是字符串起始地址的间接地址,否则会出现“address wrong”的报错。
  • 在实现最后打印语句printf("\n(1st number) %g divided by (2nd number) %g = %g", (double)num1, (double)num2, num3 );时,需要将后面的参数的地址连续存放在提示串的后面才能正确显示,同时也需要注意参数的大小,双精度浮点数参数要分配8个地址单元(DLX按字节编址寻址,且双精度浮点数为64-bit)。
  • movi2fp-cvi2d指令存在浮点寄存器f1的数据相关导致的数据冲突。这两条指令的f2也有同样的情况。解决这种控制冲突可以通过将movi2fp在MEM阶段末的值转发给cvi2d的EX阶段使用。
  • cvi2d-sd指令之间存在浮点寄存器f2之间的数据相关导致的数据冲突。解决这种数据冲突可以通过在cvi2d指令的EX阶段或者是在MEM阶段进行转发,转发到sd指令的MEM阶段来解决数据冲突。
  • divd-sd指令之间存在浮点寄存器f8的数据相关导致的数据冲突。因为浮点除法指令的在EX阶段需要执行的周期为4个周期,因此只使用转发也不能解决冲突,还得需要加以停顿。
  • 分析使用转发和不使用转发的性能差异。可以发现在使用转发部件后程序的执行周期由153降到了132,RAW停顿由43下降到了27,由CPU性能方程可知,程序整体性能得到提升。

  • 观察指令bnez r1,num2notzero。如果不分支发生,程序数据分析就如上图所示,即进行浮点的除法运算。如果分支发生(因为除数为0)则直接输出提示信息并跳转到结束处,数据分析如下图所示。对比可以发现,在使用转发部件的情况下,分支不发生程序执行所需的时钟周期是132,而分支发生所需的时钟周期是121;在不使用转发部件的情况下,分支不发生程序执行所需的时钟周期是153,分支发生所需的时钟周期是137。可以得出结论,在分支发生时程序所使用的时钟周期更短,程序整体性能更高。