【问题标题】:write russian peasant multiplication with mips用 mips 编写俄罗斯农民乘法
【发布时间】:2018-04-01 16:20:20
【问题描述】:

我想编写一个使用 mips 的俄罗斯农夫乘法的程序,但我遇到了一些问题

/ A method to multiply two numbers using Russian Peasant method
unsigned int russianPeasant(unsigned int a, unsigned int b)
{
    int res = 0;  // initialize result

    // While second number doesn't become 1
    while (b > 0)
    {
         // If second number becomes odd, add the first number to result
         if (b & 1)
             res = res + a;

         // Double the first number and halve the second number
         a = a << 1;
         b = b >> 1;
     }
     return res;
}

我用 MARS 环境翻译成 mips

    .data
msg1: .asciiz "give your first number"
msg2: .asciiz "give the second number"

    .text
#a = $t1
#b = $t2
li $t3,0 #p

li $s1,1
li $s2,2

#display msg1
li $v0,4
la $a0,msg1
syscall
#read first number
li $v0,5
syscall
move $t1,$v0

#display msg2
li $v0,4
la $a0,msg2
syscall
#read second number
li $v0,5
syscall
move $t2,$v0

WHILE: 
    bgt $t2,$zero,DO
    move $a0,$t3
    li $v0,1
    syscall
    li $v0,10
    syscall

DO:
     div $t2,$s2
     mfhi $s0 #r $s0 = $t2 / $s2
     beq $s0,$s1,INC # IF b MOD 2 = 1 I jump to INC
    #a = 2xa
     mul $t1,$t1,$s2

     div $t2,$s2
     mflo $t2 # b = $t2/$s2
     j WHILE 

# i = i+ 1   
INC:
    add $t3,$t3,$t1
    jr $ra 

拜托,有人可以帮助我吗? 修复“错误:无效的程序计数器值:0x00000000” 我搜索了如何解决它,但我遇到了 $ra 的问题 如何在 $ra 中正确保存返回地址?

【问题讨论】:

  • 最后一行的jr $ra 应该实现什么?看起来像 MARS/SPIM 环境(从 syscall 使用情况猜测),其中 $ra 在程序启动时为零,我看不到任何 jalmove/add/... 会改变 $ra,所以它看起来您面临的问题是在代码中添加随机指令,而不是弄清楚您到底想做什么,并且只做那个。
  • 是的,我知道但不知道如何在我的代码中使用 jal,它可能会破坏我的代码。是的,我使用 MARS 环境
  • jal 是跳转,它还设置了$ra 的地址为后面的指令(或下一条,甚至不确定是哪一条,因为 MIPS CPU 的延迟分支槽,这使得所有跳转更多复杂..您的代码看起来期望该功能已关闭,因此您的 MARS 正在模拟没有分支延迟槽的虚拟简化 MIPS CPU)。那么最后一条指令应该实现什么?你为什么把jr $ra放在那里?要结束代码执行,请使用带有终止功能的 syscall。要跳回您的代码,请使用普通跳转到标签。要使用“子程序”,请学习如何使用jal+jr $ra
  • 我只是编辑我的帖子,我希望你明白我现在在寻找什么
  • 我明白你想要编写什么代码,我不明白你是如何想出你的代码的 - 好吧,我确实明白如何,因为你只是学习汇编,你没有花足够的时间+练习 CPU 指令集,你还不了解一些常见的习语是如何在汇编中完成某些事情的,但是结果对于长时间看你写的所有东西是有好处的,交叉-检查你的讲座和可用的说明,并试图找出看起来合理的东西,以及从 C 源代码转移的令人费解的混乱太多。然后删除它,重新开始。

标签: algorithm math assembly mips


【解决方案1】:

jr $ra 将跳转到存储在寄存器$ra 中的地址,该地址不是由您的代码设置的,因此它仍然为零(在运行您的代码之前来自 MARS 的初始寄存器值)。然后 CPU 将跳转到地址 0,这是故障,并报告。

要使用jr $ra从某些代码中“返回”,您必须首先使用jal指令(或其他修改$ra内容的指令)来设置$ra。此外,当您想嵌套多个jal“子程序调用”时,您必须存储/恢复外部调用的$ra,以免下一个嵌套的jal 丢失值。

以下代码是使用配对 jal+jr $ra 进行类似“子程序”的调用的示例,以及此类算法在汇编中的实现多么简单的示例(因为实际上 C 源代码更像是在C,因此您的经验不足使您采用了一种非常复杂且复杂的方法,可以使用原始 C 源代码以几乎 1:1 的比例在汇编中实现):

.text
main:  # just minimal "main" to verify the code works
    # read two integers as input
    li      $v0,5
    syscall
    move    $a0, $v0        # $a0 = a
    li      $v0,5
    syscall
    move    $a1, $v0        # $a1 = b
    # call the russianPeasant subroutine
    jal     russianPeasant  # $v0 = a * b
    nop
    # output result
    move    $a0, $v0
    li      $v0, 1
    syscall
    # terminate
    li      $v0, 10
    syscall

还有子程序本身,可以使用a0a1 中的参数调用,并在v0 中返回结果。

# input: $a0 = a, $a1 = b
# output: $v0 = a * b
russianPeasant:
    li      $v0, 0          # res = 0
    beq     $a1, $zero, russianPeasant_b_zero
    nop     # neutralize "Delayed branching" setting ("nop" works with both ON/OFF setting)
russianPeasant_while_b:
    andi    $at, $a1, 1     # test if b is odd, for even b skip the res = res + a
    beq     $at, $zero, russianPeasant_b_even
    nop     # but such neutralization comes with performance cost of course
    add     $v0, $v0, $a0   # res = res + a
russianPeasant_b_even:
    sll     $a0, $a0, 1     # a = a << 1
    srl     $a1, $a1, 1     # b = b >> 1
    bne     $a1, $zero, russianPeasant_while_b  # repeat when (b != 0)
    nop     # on real MIPS production code the instructions are reordered to avoid useless nop
russianPeasant_b_zero:
    jr      $ra             # return res
    nop

代码有意在每个分支 nop 指令之后放置,以使其在延迟分支设置 ON 或 OFF 的情况下工作。

尝试使用带有指令描述的 MARS 帮助 (F1) 来弄清楚它是如何工作的,还可以使用调试器的单步功能来观察正在运行的代码,一次一条指令,观察所有寄存器值和代码流。

在具有延迟分支的真实 MIPS CPU 上,可以像这样优化代码(您可以在 MARS 设置中打开“延迟分支”以使其模拟具有这种有点令人困惑的行为的真实 MIPS CPU .. 从您的原始来源看起来就像您正在学习 MIPS 程序集而没有延迟分支,这对于刚开始使用程序集的人来说当然是合理的方法,但这不是真正的 MIPS CPU 的工作方式):

# input: $a0 = a, $a1 = b
# output: $v0 = a * b
russianPeasant_real_MIPS:   # second variant supporting delayed branching like real MIPS CPU
    beq     $a1, $zero, russianPeasant2_b_zero
    li      $v0, 0          # res = 0
russianPeasant2_while_b:
    andi    $at, $a1, 1     # test if b is odd, for even b skip the res = res + a
    beq     $at, $zero, russianPeasant2_b_even
    srl     $a1, $a1, 1     # b = b >> 1
    add     $v0, $v0, $a0   # res = res + a
russianPeasant2_b_even:
    bne     $a1, $zero, russianPeasant2_while_b  # repeat when (b != 0)
    sll     $a0, $a0, 1     # a = a << 1
russianPeasant2_b_zero:
    jr      $ra             # return res
    nop

【讨论】:

  • @kevin73 ??当然它可以工作,但这不是重点,重点是,你是否会理解它是如何工作的......继续研究和尝试调试器,并在 cmets 中询问我是否有无法解读的内容。如果您只是将其复制并作为您的工作提交,那您就是在浪费每个人的时间。
  • 哦,好吧,我只是看到我是一个真正的新手,所以我必须学习很多
  • @kevin73 是的,但没关系,装配的学习曲线相当陡峭。其实没那么难,就是个杂草丛生的简单计算器,仅此而已,但要花些时间才能深入你的脑海,你会学会这种类似机器的思维方式,纯粹的数学+确定性的方法,不会让你的大脑通过你所希望的或一些错误的假设来转移你的真实状态。在获得了机器的感觉之后,您可能会发现该程序集令人惊讶地“简单”地理解(这是实现某事所需的代码量,这使得它“复杂”)。
猜你喜欢
  • 2010-09-20
  • 2013-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多