verilog编写电路中的时序逻辑与组合逻辑分离

P1_L1_voter_plus时序逻辑与组合逻辑的分模块编写&&阻塞赋值、非阻塞赋值 一.问题提出 ​ 在完成P1_L1_voter_plus一题时,由于需要对输入中1的位数进行统计,我第一次使用到for循环语句来统计输入中1的个数,原码类似于下例。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 module test_for ( input [7:0] in, input clk, input rst, output reg [7:0] out ); integer i; always @(posedge clk or posedge rst) begin : test if(rst) begin out <= 8'b0; end else begin for(i=0;i<8;i=i+1) begin if(in[i]) out <= out + 1'b1; end end end endmodule ​ 此段代码并不能成功实现在一个时钟周期内实现统计个数的功能。仿真如下: ...

October 15, 2023 · 3 min · sudo

verilog

verilog(P1课前复习) 长文,做好战斗准备! 1.同步复位与异步复位 1.同步复位 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 //复位信号高电平有效 always@(posedge clk) begin if(clr) begin status <= 4'b0000; end else begin //statement end end //复位信号低电平有效 always@(posedge clk) begin if(!clr) begin status <= 4'b0000; end else begin //statement end end 2.异步复位 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 //复位信号高电平有效 always@(posedge clk or posedge clr) begin if(clr) begin status <= 4'b0000; end else begin //statement end end //复位信号低电平有效 always@(posedge clk or negedge clr) begin if(!clr) begin status <= 4'b0000; end else begin //statement end end ​ 异步复位将clr加入敏感符号列表 ...

October 14, 2023 · 5 min · sudo

P1_L1_BlockChecker

语句块模拟检查 P1_L1_BlockChecker 一.题目要求 1.题目描述 ​ 输入ASCII字母和空格,一个或多个连续出现的字母构成单词,单词不区分大小写,单词之间由一个或多个空格分隔开。检查工具检查自复位之后的输入中,begin和end是否能匹配。 注: 匹配的begin必须出现在end之前 一个begin只能匹配一个end 允许出现嵌套 出现不能按照规则匹配的begin或end,则匹配失败 保证在模块使用前进行复位 2.IO定义 信号名 方向 描述 clk I 时钟信号 reset I 异步复位信号 in[7:0] I 当前输入的ASCII码 result O 当前输入能否完成begin和end匹配 注意:输出result为“当前”的判断结果,即随着状态更新 2.状态转移图 解释: 所有的单词都以空格表示结束对应回到S0状态 如果之前没有出现过begin就出现end,之后无论输入什么都会输出0,设置为单独的状态S10,这个状态是自环的,无论输入什么都会回到S10,并输出0. 3.verilog代码实现 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 module BlockChecker ( input wire clk, input wire reset, input wire [7:0] in, output reg result ); reg [31:0] begin_cnt; reg [15:0] status; `define S0 16'b0000_0000_0000_0001 `define S1 16'b0000_0000_0000_0010 `define S2 16'b0000_0000_0000_0100 `define S3 16'b0000_0000_0000_1000 `define S4 16'b0000_0000_0001_0000 `define S5 16'b0000_0000_0010_0000 `define S6 16'b0000_0000_0100_0000 `define S7 16'b0000_0000_1000_0000 `define S8 16'b0000_0001_0000_0000 `define S9 16'b0000_0010_0000_0000 `define S10 16'b0000_0100_0000_0000 always @(posedge clk or posedge reset) begin if(reset) begin begin_cnt <= 32'b0; status <= `S0; result <= 1'b1; end else begin case (status) `S0: //empty begin if(in == " ") begin status <= `S0; end else if (in == "e"||in == "E") begin status = `S7; end else if (in == "b"||in == "B") begin status = `S1; end else begin status = `S6; end end `S1: //b begin if(in == "e"||in == "E") begin status = `S2; end else if (in == " ") begin status <= `S0; end else begin status <= `S6; end end `S2: //be begin if(in == "g"||in == "G") begin status <= `S3; end else if (in == " ") begin status <= `S0; end else begin status <= `S6; end end `S3: //beg begin if(in == "i"||in == "I") begin status <= `S4; end else if (in == " ") begin status <= `S0; end else begin status <= `S6; end end `S4: //begi begin if(in == "n"||in == "N") begin status <= `S5; result <= 1'b0; end else if (in == " ") begin status <= `S0; end else begin status <= `S6; end end `S5: begin if(in == " ") begin status <= `S0; begin_cnt <= begin_cnt + 1; end else begin status <= `S6; if(begin_cnt == 32'b0000) begin result <= 1'b1; end else begin result <= result; end end end `S6: begin if(in == " ") begin status <= `S0; end else begin status <= `S6; end end `S7: begin if(in == "n"||in =="N") begin status = `S8; end else if (in == " ") begin status <= `S0; end else begin status <= `S6; end end `S8: begin if(in == "d"||in == "D") begin if(begin_cnt == 32'b0001) //只剩余一个begin 可以完成配对 begin status <= `S9; result <= 1'b1; end else begin status <= `S9; result <= 1'b0; end end else if (in == " ") begin status <= `S0; end else begin status <= `S6; end end `S9: begin if(in == " ") //确定匹配到的一定是end begin if(begin_cnt == 32'b0) //还没有出现过begin 这种情况无论后便出现什么都是0 begin status <= `S10; result <= 1'b0; end else if(begin_cnt == 32'b1) begin status <= `S0; result <= 1'b1; begin_cnt = 32'b0000; end else if(begin_cnt > 32'b1) begin status <= `S0; result <= 1'b0; begin_cnt = begin_cnt - 32'b0001; end end else //匹配到的不是end begin status <= `S6; if(begin_cnt==32'b0) begin result <= 1'b1; end else begin result <= 1'b0; end end end `S10: begin status <= `S10; result <= 1'b0; end default: begin status <= status; result <= result; end endcase end end endmodule 4.一个坑点 `S5: begin if(in == " ") begin status <= `S0; begin_cnt <= begin_cnt + 1; end else begin status <= `S6; if(begin_cnt == 32'b0000) begin result <= 1'b1; end else begin result <= result; end end end ​ 在进行单词begin的匹配时,需要对是否成功的匹配到begin做判断,一开始在两种之间来回改,后来才想到这是两种情况应该使用分支结构: ...

October 13, 2023 · 4 min · sudo

P1_L3_ALU && Verilog中的符号处理

P1_L3_ALU && Verilog中的符号处理 1.问题引入 AC? WA? ​ 在做P1课下提交中的6处理ALU时,我发现了一件怪事,两个看似只是添加了变量差别的程序居然跑起来一个能够通过测试,一个不能,下面附上AC代码和WA代码。 AC代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 module alu ( input [31:0] A, input [31:0] B, input [2:0] ALUOp, output [31:0] C ); wire [31:0] add = A + B; wire [31:0] sub = A - B; wire [31:0] with = A & B; wire [31:0] huo = A | B; wire [31:0] logic_shift = A >> B; wire [31:0] math_shift = $signed(A) >>> B; assign C = (ALUOp == 3'b000) ? add: (ALUOp == 3'b001) ? sub: (ALUOp == 3'b010) ? with: (ALUOp == 3'b011) ? huo: (ALUOp == 3'b100) ? logic_shift: (ALUOp == 3'b101) ? math_shift: 32'b000; endmodule WA代码 ...

October 12, 2023 · 2 min · sudo

P0-logisim

CO-P0-logisim ​ 写在前面:10.9晚上机有三道题目,笔者只侥幸通过了其中两道,记录一下思路,分享一下拙见QAQ,如有错误还请指正!(os:上机还是很辛苦的……) 1.找最小 1.思路探究 ​ 题目的大意为,输入五个八位二进制数字,完成电路,输出没有出现过的最小正整数,例如,输入为:0,3,2,1,7,那么最小的没有出现过的正整数为4. ​ 题意分析:如果能够想到将输入转换为独热码就可以比较直观地理解问题!我们考虑:这个所求的最小正整数最大是多少?可以想到,找最小正整数的过程类似于找一个“空隙”,如果我们将五位输入按从小到大顺序排好,记为a[n],那么最小正整数会出现的条件: 1 a[n-1] + 1 < a[n] ​ 我们可以发现:最小正整数最大的时候即为五个输入为1,2,3,4,5;此时最小正整数大小为6.故我们可以知道最小正整数的出现范围为16。这一点对我们的解题过程很关键,因为这一认知告诉我们:无论输入如何,我们只需要去找从16之间没有出现过的最小数即为我们所求的最小正整数。 ​ 然后:我们怎么知道数字有没有出现过呢?常规的二进制编码可能较难实现,但是选择独热码是一个比较直观的选择,将每个输入转换为独热码,最后或起来…….到这里思路已经明晰,实现电路吧! 2.电路设计 1.转换为独热码电路 one-hot encoder ​ 这里做出判断,大于6就当作0处理,需要注意的是,八位独热码中0也被编码,即为图中所示,因为要求是正整数,所以无论是否输入0,第0位应该被默认占位,这一点在主电路中也有处理。 2.main ​ 在主电路的处理中,我们添加一个“输入0”,将第0位占位,保证是正整数。 2.回字迷宫 ​ 从迷宫中的1位置出发,绕回字迷宫行走,00向北,01向南,10向西,11向东,如果当前输入的方向有位置,就输出进入位置的编号,若没有位置,就输出当前位置编号,使用mealy状态机实现,注意1位置的编号为1。 ​ 这个题目比较简单,无脑的把状态逻辑和输出逻辑分开列真值表就可以解决……题目里一共八个状态,建议还是使用四位编码吧,从0001开始编码,这样可以在输出逻辑时好看一些,笔者采用三位编码,导致输出时的数字要比状态数字大1,有点费心神。 ​ 打表技巧:将输入利用splitter分解成1位输入到模块中,这样在模块中便于进行analyze功能和debug……毕竟将多位传输进模块,在打完表之后要进行位拼接,这个过程走线比较逆天,另外如果逻辑错了不好进行debug,需要将拼接好的位数再展开……. 3.十六进制匹配 ​ 此题笔者是通过打真值表实现,比较麻烦,下面附上状态转移图 ​ 课程组提供的标准解法中利用三个寄存器存储最近输入的状态,这种做法比较巧妙,引用讨论区中助教的回答

October 9, 2023 · 1 min · sudo

P0课下提交

P0课下提交 ​ 本次P0课下提交部分全部为logisim有关内容,五个电路题目我没有一题是一遍过的QAQ,在做每个题目的过程中或多或少都会发现一些疏漏点,本篇笔记的初衷是记录一下做题思路(毕竟.circ文件不支持添加笔记)以及在做题过程中遇到的一些坑点。 1.CRC校验码的生成 名称 方向 描述 A[7 : 0] I 8位原数据帧 B[3 : 0] I 除数 C[10 : 0] O 8位原数据帧+3位余数 1.被除数的生成 ​ 被除数为8位原数据帧 + (除数位数-1)位0,在题目中具体下来即补全为11位被除数。这时我们发现,按照题目中的提示,按照4位除法来搭建电路,而一个11位数应当可以进行8次4位补位除法(同样参考商的位数是8位可以得到答案,不要被竖式计算过程中似乎计算除法模块小于8次迷惑,做出难以名状的事情)。 2.模二除法 ​ 在此题目中涉及到模二除法的使用,这是一个新概念,模二除法在结果上等于两位进行异或的答案,但是进行模二除法的前提是最高位需要为1(已经保证除数的最高位为1),即保证被除数与除数的最高位相同。这样我们可以知道,能进行除法的四位数为```1xxx```,这样得到的余数为```0xxx```,型为```0xxx```的数字不满足进行模二除法的条件,需要进行借位,直到最高位变为1才进行计算,这是根据题目中给出的样例得到的。 3.电路设计 ​ 通过以上分析我们知道,当前补全的四位能否进行模二除法的关键在于它的最高位,如果当前四位数字的最高位为1则进行模二除法,并传递余数到下一级,如果当前最高位为0则将数字左移一位,传递给下一级,如此传递直到最高位为1满足进行除法的条件。由于我们知道余数一定是三位且在进行下一级除法前需要拼接被除数的下一位,因此在四位除法中输出设计为3位。下面给出电路。 2.主电路搭建 ​ 主电路主要实现8级除法的连接(传入下一位),这里需要注意的是在进行输出输出时都需要进行处理,输入时在原数据后补加3位0,输出时在原数据后补加3位余数。此题用到许多的splitter。 2.实现GRF ​ 这一题主体上的功能比较单一,即对寄存器进行简单的读写操作,但是这个题目中却有很多的细节值得细细品味。而且此题的电路图过于复杂且重复,因此只展示部分电路连接。 1.一个弱智问题MUX与DMX ​ 对于MUX与DMX我要好好品味,毕竟Ppre挂掉就是因为对多路选择器的功能不够熟悉QAQ!。 1.DMX ​ DMX通常用于输入端选择输入到哪里的情况,短边连接输入信号,长边连接多个可以被选择的输入到的位置。 端口说明: 短边连接输入信号(data) 长边连接多个输入路径选择 腰上一个使能端口(include enable),一个选择输入到第几个路径的输入信号(select),这里需要注意的是一些情况下可以不选择启用使能端口,在启用使能端口时,腰上会出现两个接口点,如何去区分功能?端口上有一个灰色点的是select! 2.MUX ​ MUX通常用于选择多方数据中的一个来进行输出,长边连接多个可以进行输出的信号,短边进行输出。 端口说明: 短边进行输出(output) 长边上连接多个可供进行输出的信号 腰上一个使能端口(include enable),一个选择输出第几路数据的信号(select),同样,上面标记有灰色点的为选择信号。 3.总结 ​ DMX用于输入到哪里的选择,MUX用于输出哪个的选择。选择信号为腰上标记灰色点的端口。DMX与MUX在此题中配对放置。 使能端的勾选视情况而定。 2.DMX的three-state ​ 在我进行电路搭建的过程中,我习惯性的将three-state设置为no,在此次搭建过程中,由于需要向32个不同的寄存器中写入数据,我发现在写入数据时每当我向新的寄存器写入数据,之前写过的寄存器会被洗掉变回0,这是一个很奇怪的现象,知道我看了讨论区,才知道要将DMX的three-state设置为yes.这背后的原因是什么呢? ...

October 2, 2023 · 1 min · sudo

Pre上机logisim部分——俄罗斯方块

Pre上机logisim部分——俄罗斯方块 一.题目的回忆 1.关于输入输出 name width input 8 reset 1 clk 1 output 2 2.题意 ​ 我们利用mealy型状态机实现俄罗斯方块的模拟。假设我们有一个1行8列的空间,在每个时钟周期进行一个8位的输入,这个输入以独热码形式,如00000001表示在第一块空间放入方块,对于放置方块的输出有如下要求: 若尝试放置处已有方块,则当前想要放入的方块被阻挡,输出01. 若尝试放置处无方块且其他位置处至少有一处无方块,则成功放入方块,输出10。 若尝试放置处无方块,此外每个位置都有方块,则清空所有方块,输出得分 11。 3.电路模块外观 ​ 这部分对于题意倒是无关紧要 后续题解补充,唯一的坑点在于我们搭建好的电路可能与标准要求的电路外观不同,涉及到修改子电路外观。 二.题意理解 ​ 对于这道题目,由于要求搭建状态机,我的思路被局限在pre教程中提示过的利用真值表的解法。事实上,mealy状态机的下一状态逻辑和输出逻辑的输入是相同的,即电路的上一状态和当前输入。我们粗略的考虑一下打表的复杂度,输入为独热码,共八种状态,可能的状态有2^8-1种,即除去满方块的状态,这样打表的复杂度是2^11显然是不合理的。 所以我们应当摒弃打表这种想法,进一步思考题目的要求。 ​ 题目中只涉及到三种状态的判断,一是放置位置处已经有方块,这时放置失败,输出01,并将原来的状态更新为只有将要放置的这一块地方有方块(即相当于输入的一行把原来的状态顶替掉),二是想要放置的位置没有方块,且其他位置至少还有一个空块,这时输出10,三是想要放置的地方没有方块,且放置后刚好满一行,清空这一行,输出得分11。 ​ 我们考虑如何判断放置位置处有没有方块呢?我们知道输入为8位独热码,只有表示方块的那一位为1,如果此时状态中那一位已经有方块,我们知道,这两位的与运算为1。经过分析我们知道,判断放置位置处是否为空可以用与运算,而且当前输入与状态的与运算八位中最多只会有一位出现1。 ​ 我们考虑如何更新状态呢?我们想要的更新状态是在可以放入方块的情况下,而这“放入”的操作是可以通过位运算“或”来实现的,注意,在填入方块后,我们还需要考虑是不是每一位都是1,如果都是1,则说明满足情况3,需要清除所有方块。 ​ 在大体明白的情况下,考虑一下细节。如何判断与运算中是否有1?将与运算的结果的8位进行或运算。如何判断或运算是否已经填满?使用与运算。 ​ 在大致逻辑明白的情况下,我们可以进行搭建电路。 三.电路搭建 ​ 这里我附上mealy状态机的原型图 ​ 我们可以知道下方的或门是用来更新状态,或门之后的与门用来判断是否清空。clk与reset信号则直接控制寄存器。这里只有一个状态更新是由上面部分的电路提供的,即放置位置已有方块,这时更新为input的状态。多路选择器选择1引脚输出,大部分情况下(2,3)都是选择0引脚输出,即下方电路产生的下一状态。同时我们可以发现,电路的输出是控制选择常量输出。 四.编辑子电路外观 在我们搭建好的电路中,默认的外观为 ​ 题目要求的外观为输出锚点在右上角,这就需要我们修改子电路外观。 ​ 如上即可正确测评。

September 24, 2023 · 1 min · sudo

MIPS中自动存入地址的指令

向$ra寄存器中自动存入地址的指令 ​ 在进行编写MIPS部分矩阵转化一题时,我误以为beq等分支指令也会将下一条指令的地址存入$ra寄存器,这导致出现访存bug. ​ 在MIPS架构的汇编指令中,只有 jal: 会将当前指令的地址存入$ra中,并跳转到目标地址执行 jalr:会将要跳转的地址存入目标寄存器,并将当前指令的地址存入$ra. beq与bne等条件分支指令则不会有将当前地址存入$ra寄存器的行为。 4560

September 14, 2023 · 1 min · sudo

MIPS中的函数调用

函数调用 一.调用初印象 ​ 最早接触到函数调用是在选择排序程序中,教学视频中代码块来换回拼接导致我看了好几遍视频! 下面附上源码 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 .data array: .space 400 // 申请数组空间 message_input_n: .asciiz "please input an integer as the length of the sequence\n" message_input_array: .asciiz "please input an integer followed with a line breaker\n" message_output_array: .asciiz "the sorted sequence is:\n" space: .asciiz " " stack: .space 100 // 申请栈空间 .globl main // 在代码段起始位置声明main为全局符号 .text input: la $a0,message_input_n li $v0,4 syscall li $v0,5 // number of integers to be sorted syscall // the number is stored in $v0 move $t0, $v0 // set $t0 to the contents of $v0 li $t1, 0 // 循环变量 for_1_begin: slt $t2, $t1, $t0 // t2=1 if t1 < t0 beq $t2, $zero, for_1_end nop // 目标指令紧跟分支指令 增加延时槽防止并行性引起错误 //下面这段代码实际上是在计算存入地址 // 存入地址 = 首地址 + 循环变量 * 4 la $t2, array / 将数组首地址存入t2 li $t3, 4 mult $t3, $t1 // t3 * t1 高位存入 hi,低位存入lo 事实上一般的乘法,只要结果不超过32位,lo中的值就是完整的答案 mflo $t3 // 将lo寄存器中移动到t3(所有的move指令都相当于赋值语句,复制后原值不会改变,而不是“移动”) addu $t2, $t2, $t3 //在首地址t2基础上加上偏移量t3 la $a0,message_input_array //输出前导字符串 li $v0,4 syscall li $v0, 5 //输入数字 syscall sw $v0, 0($t2) // 从寄存器存入数组中对应的地址 这里的t2就是刚刚计算过的地址 addi $t1, $t1, 1 // 循环变量++ j for_1_begin nop for_1_end: move $v0, $t0 jr $ra //跳回到主程序,跳转语句的下一条语句 nop //与input同理 无需多言! output: move $t0, $a0 li $t1,0 la $a0,message_output_array li $v0,4 syscall for_2_begin: slt $t2, $t1, $t0 beq $t2, $zero, for_2_end nop la $t2, array li $t3, 4 mult $t3, $t1 mflo $t3 addu $t2, $t2, $t3 lw $a0,0($t2) li $v0,1 syscall la $a0,space li $v0,4 syscall addi $t1, $t1, 1 j for_2_begin nop for_2_end: jr $ra nop sort: addiu $sp,$sp,-32 //向低地址移动32字节 move $t0,$a0 //此时a0值即为元素个数 li $t1,0 //循环变量 for_4_begin: slt $t2, $t1, $t0 //选择排序外层循环n-1趟 beq $t2, $zero, for_4_end nop //计算地址 la $t2, array li $t3, 4 mult $t1, $t3 mflo $t3 addu $t2, $t2, $t3 move $a0, $t0 move $a1, $t1 //父函数维护t寄存器 入栈 需要注意的是多层调用时$ra的维护 sw $t2, 28($sp) sw $t1, 24($sp) sw $t0, 20($sp) sw $ra, 16($sp) //这时的ra值需要保存 这里的ra值记录的是返回到主函数的指令地址 经过调用findmin后会变为返回到sort的地址! jal findmin //调用子函数 nop //出栈 lw $ra, 16($sp) lw $t0, 20($sp) lw $t1, 24($sp) lw $t2, 28($sp) //交换值 v0地址与t2地址处存储的值,两个寄存器分别存储 lw $t3, 0($v0) lw $t4, 0($t2) sw $t3, 0($t2) sw $t4, 0($v0) addi $t1,$t1,1 //更新循环变量 j for_4_begin nop for_4_end: addiu $sp,$sp,32 //将申请的栈空间退回,栈指针回到高地址 jr $ra //回到主程序 nop findmin: //从sort中传入 a0值为n, a1值为外层循环变量i la $t0,array sll $a0,$a0,2 subi $a0,$a0,4 //需要注意的是在 * 4的基础上需要减去4, addu $t0,$t0,$a0 //当前的地址是数组中最后一个元素的地址 lw $t1, 0($t0) // t1=a[n-1] move $t2,$t0 move $t3,$t0 // t3 = t2 = t0 = 最后一个元素地址 // a[i+1] la $t0,array sll $a1,$a1,2 addu $t0,$t0,$a1// t0此时为a[i+1]地址 for_3_begin: sge $t4,$t3,$t0 // t4 = 1 if t3 >= t0 beq $t4,$zero,for_3_end nop lw $t5,0($t3) // t5=a[n-1] // 进入查找时 t3为最后一个元素地址 ,t0为a[i+1]地址 //这里寻找最小值的操作实际上是从末尾开始的,先记t1=a[n-1]为最小值,之后由哨兵 t5=遍历到的值 从n-1逐步向前遍历直到 i+1 //不断更新t1的值作为新的最小值,并在t2中保存最小值地址 t3 //这里t1被设置为保存最小值 如果当前遍历的元素小于t1最小值则进入下方更新最小值操作,否则进入if_1_else,顺序进入if_1_end遍历 //下一个元素 slt $t6,$t5,$t1 // t6 = 1 if t5 < t1 第一次运行时 t5 = t1 直接跳转到 if_1_else beq $t6,$zero,if_1_else nop move $t1,$t5 //更新最小值 move $t2,$t3 //保存最小值在数组中的的地址 j if_1_end nop if_1_else:// if_1_else后误操作则直接执行 if_1_end 在标签之间无跳转时按照顺序执行 if_1_end: subi $t3,$t3,4 //t3从末尾向前移动一个元素,更新遍历元素 j for_3_begin nop for_3_end: move $v0,$t2 jr $ra //回到主程序 nop main: la $sp, stack addiu $sp,$sp,100 //此处sp+100是将指针向高地址移动 addiu $sp,$sp,-20 //-20向低地址移动 jal input //跳转到input nop move $t0,$v0 //回到位置 move $a0,$t0 sw $t0, 16($sp) //向高地址偏移量为16 调用者维护t寄存器,将t0入栈 jal sort nop lw $t0,16($sp) // t0出栈 move $a0,$t0 jal output nop addiu $sp,$sp,20 li $v0,10 //程序结束 syscall ​ 上述选择排序算法较为复杂,主体结构为main调用input,sort,output函数,在sort中又调用findmin子函数 ...

September 13, 2023 · 9 min · sudo

MIPS基础编程

MIPS编程 1.各部分寄存器的功能 $t0 ~ $t9: 临时变量 (调用者保存,否则容易丢失) $s0 ~ $s7: 保存变量(被调用者保存) $0 :常量0,存不了数据 $at : 保留给汇编器(不可以随便用) $v0 ~ $v1 : 函数调用返回值 $a0 ~ $a3 : 函数调用参数 $sp : 栈指针 $ra : 返回地址,用于子程序的调用 2.调用子过程时对于s,t寄存器的操作 1.被调用者维护s寄存器 ​ 对于s寄存器而言,被调用者需要保证s寄存器中的值在调用前后不能发生改变, 实际操作中,如果想要编写一个子函数,那么在这个子函数中使用的所有s寄存器,都必须要在函数的开头入栈、在函数的结尾出栈,确保s寄存器的值在函数调用前后不会发生变化。 2.调用者维护t寄存器 ​ 对于t寄存器,编写子函数中用到t寄存器的地方无需做任何保存,维护t寄存器是上层函数(调用者维护),调用者将t寄存器压入栈中,函数调用结束之后再弹回来,只需要借助$sp指针。 需要注意的是,对于s,t寄存器的维护都要通过在数据区开辟栈空间来实现! 3.调用关键字 jal:跳转到子过程 jr:跳转到父过程 4.栈的使用 过程自身需要满足栈的结构 过程调用子过程时需满足栈的结构 子过程执行前后移动栈指针 $sp MIPS中栈由高地址向低地址延申,即优先使用高地址,父过程栈帧高,子过程栈帧低 子过程的栈帧图 高地址 临时变量 ​ 返回地址 ​ 需要保存的寄存器 ​ 其他变量 低地址 参数0~3,传给子子过程($a0~$a3) 叶子函数:可以省去参数和返回地址(无子子函数) 栈的具体使用 计算好栈帧大小 即保存这些变量需要的字节大小 栈指针减少表示向低地址移动,栈指针增加表示向高地址移动 栈指针始终指向栈顶,栈指针初始时在高地址 过程开始时分配栈空间 addiu $sp,$sp,-32(需要32字节,将栈指针向低地址移动32字节) 过程结束时回收栈空间 addiu $sp,$sp,32 以栈指针为基址进行存取 sw $t0,24($sp) ,偏移量的单位是字节 偏移量为正向高地址偏移,偏移量为负向低地址偏移 5.nop延时槽 ​ 在MIPS编程中,延时槽是指指令执行的时间间隔。当某个条件分支指令(如条件跳转、函数调用等)的目标指令紧跟在该分支指令后面时,需要插入一个延时槽指令,以免出现错误的指令结果,常用```nop```. ...

September 13, 2023 · 2 min · sudo