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

OOpre_HW3

OOpre_HW3 and JUnit 一.关于OO_checkstyle的新发现 只能采用驼峰命名法命名变量 方法行数不超过60行(后续重构代码将操作与处理输入分离的主要原理,虽然只有两分) 每行字数不超过100(方法传参时发现) 其余关于空格的问题省略 二.增量开发的思路 在此次作业中,新增了“食物”、“背包”等概念。food作为与equipment和bottle同级物品,背包则负责容纳这些物品。 新增操作: 尝试携带(放入背包)某物品(保证尝试携带的物品冒险者已经拥有) 尝试使用某物品(该物品必须被携带才能够使用) ​ 实现逻辑:我们需要明白“携带”与“使用”的业务逻辑。 我的第一版代码实现思路 ​ 我第一版代码中,按照题目描述,将food与package作为新建类处理,冒险者与背包之间的关系使用哈希表处理,建立起<advid,package>的映射,在背包中建立三个容器分别存储瓶子,装备和食物。对于加入背包,我的理解是,为冒险者增加物品是将物品放在冒险者对应的类adventure.java中对应的总库三个容器中,加入背包需要将物品从总库移动到与冒险者对应的背包,从物理角度来看是对物品进行了移动。这导致实现起来非常麻烦,例如统计数量等都需要考虑两个部分。这与题意不符,具体体现在中测最后一个数据点不过。 第二版代码 ​ 经过与助教的沟通,我理解到: 放入背包是一个概念的问题,而不是一个物理上的问题。放入背包并不需要将物品从总库中删除,只需要加入背包。 一开始处理中建立冒险者与背包对应哈希表的想法并不符合面向对象的逻辑,这是面向过程的思路,如果想要具体实现背包应该建立在冒险者类中 既然放入背包是一个概念问题,那么我们完全可以不去实现背包实体,而只需要进行概念上的判断。例如给每个物品增加一个属性 1 private boolean becarried ; 初始时设置为false即不在背包中,放入背包即建立方法将属性设置为true.这个思路在实现代码上是十分简便的,具体体验到的优势如下: 不需要新建数据结构存储放在背包中的物品 判断该物品是否在背包中只需要获取属性becarried 删除物品只需要在adventure类中的总库删除,实现简洁 获取物品数量是需要获取总库中的数量 三.代码架构与重构 ​ 经过checkstyle与JUnit对于代码架构的步步限制,我经历了三次代码重构,第一次是在编写过程中发现方法的行数不能超过60行,第二次是在传参时受到限制,选择将定义的静态方法从operation.java移动到inputhandler.java,第三次是编写JUnit过程中由于不能进行输入输出重定向等测评机认为的违法操作,这样只能将输入集中到一个类中,后续在方法中进行读取已经存储好的输入。 ​ 对于输入的类,原码如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import java.util.Arrays; import java.util.Scanner; import java.util.ArrayList; public class Main { public static void main(String [] args) { ArrayList<ArrayList<String>> inputInfo = new ArrayList<>(); // 解析后的输入将会存进该容器中, 类似于c语言的二维数组 Scanner scanner = new Scanner(System.in); int n = Integer.parseInt(scanner.nextLine().trim()); // 读取行数 for (int i = 0; i < n; ++i) { String nextLine = scanner.nextLine(); // 读取本行指令 String[] strings = nextLine.trim().split(" +"); // 按空格对行进行分割 inputInfo.add(new ArrayList<>(Arrays.asList(strings))); // 将指令分割后的各个部分存进容器中 } InputHandler inputHandler = new InputHandler(inputInfo); inputHandler.solve(n); } } ​ 这样所有的操作指令被以分割的字符串的方式存入inputinfo,后续将inputinfo传入inputhandler类进行处理,所有的变量从这个形式上的二维数组中读取。这样可以避免在编写JUnit时无法控制台输入导致无法测试方法导致覆盖率不够,第二部分任务寄掉的问题。 ...

September 22, 2023 · 2 min · sudo

OOpre_HW2

OOpre_HW2 第一次进行类的编写(冒险者故事的开端) 1.什么是面向对象(Object Oriented) ​ 对象能够直接反映现实生活中的事物,例如人、车、小鸟等,将其表示为程序中的对象,每个对象都有各自的状态特征(属性)以及行为特征(方法),除了可以存储数据外还可以对自身进行操作,相当于结构体与函数的封装。 ​ 面向对象就是把构成问题的事物分解成一个一个的对象,建立对象不是为了实现一个步骤,而是描述某个事物在解决问题中的行为。 ​ 类是面向对象中的一个很重要的概念,类是很多个具有相同属性和行为特征的对象所抽象出来的,对象是类的一个实例。 2. OO三大特征 封装 继承 多态 3.类与对象 ​ 类表示一个共性的产物,是一个综合的产物,而对象是一个个性的产物,类必须通过对象才可以使用,对象的所有操作都在类中定义 类由属性和方法组成 属性:特征 方法:行为 ​ 一个类想真正地进行操作则必须依靠对象 1 2 3 4 5 //对象的定义 classname objectname = new classname();//所有类的对象都是通过new关键字创建 //访问类中的属性或方法 objectname.id //访问属性 objectname.func(parameter1,parameter2)//调用方法 类的编写规则 类必须编写在.java文件中 一个.java文件中可以存在多个类,但只能存在一个public修饰的类 .java文件名必须与public修饰的类名相同 同一个包中不能有重名的类 4.构造方法 ​ 在创建对象时,调用构造方法,所有的JAVA类中都至少存在一个构造方法(除了主类),如果一个类中没有明确的编写构造方法,编译器会自动生成一个无参的构造方法,构造方法中没有任何的代码!如果自行编写了构造方法,则编译器不会生成无参的构造方法。 构造方法的定义格式 构造方法名称必须与类名相同 没有返回值类型的声明 1 2 3 4 5 6 7 //创建一个对象就要调用构造方法 //一个自定义构造方法的例子 public person(String name, int age) { this.name = name; this.age = age; } this关键字 this指当前对象 程序中非静态方法可以使用this关键字 指向当前代码运行时所处于的对象空间 引用当前对象的实例变量 目前只在构造方法中接触this关键字 static关键字 static修饰变量为静态变量,也成称为类变量,静态变量属于类本身,而不是属于对象实例。该类的所有对象共享同一个静态变量的值,不会开辟出多块内存空间,可以通过<类名>.<变量名>来访问静态变量,但此时的变量需要被public修饰而不是private ...

September 15, 2023 · 6 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

MIPS与计算机内存

COpre——MIPS汇编指令与机器码、内存 ​ 在笔者进行COpre中MIPS部分的学习时,对于MIPS指令及其机器码转换,MIPS对应的内存计算等等感到十分头痛,这一篇文章主要目的是零碎的记录一些知识。 一. 数制 ​ 在计组学习中最常用到的数值即为二进制(binary/BIN)和十六进制(hexadecimal/HEX),例如MIPS汇编指令机器码用二进制表示,而计算机内存的表示通常为十六进制。一位十六进制数相当于四位二进制数。一位二进制数通常又称为比特位(bit),关联出计算机系统中经典的换算关系。 ​ 在32位系统中: 1字节(byte)=8比特位(bit) 计算机中最小的寻址单位即为字节/B 1个字(word)=4字节(byte)=32bits 即一个字就是32位,同样64位系统中一个字是64位 1KB=1024B 计算机中K的概念是2^10即1024 1MB=1024KB 1G=1024MB 二. 浅析MIPS架构 ​ MIPS是一种经典的RISC架构,具有精简的指令集,32位定长指令,五级流水线,延迟槽,32个通用寄存器等特点。这里我们主要谈及32位定长指令、32个通用寄存器、指令集等内容,其他的部分会在日后涉及。这里需要特殊说明的是我们的MIPS是32位系统 1.32位定长编码 ​ 定长指令的优点是简化指令解析,减少解析时间。但同样的,由于指令为定长32位,这对于内存是不友好的。我们在写MIPS汇编程序时,编写的每一行指令代码均为32位,四个字节,一个字。这里指令具体的分为R型、I型、J型指令 R型指令(register type) R型指令用于寄存器之间的操作,常用于算术运算、逻辑操作和寄存器之间的数据传输,如add,sub.and.or等。 I型指令(immediate type) ​ I型指令用于立即数(常数)与寄存器之间的操作,通常用于家在常熟、内存读写、分支跳转等操作。例如,addi,lw,sw,beq等指令 J型指令(jump type) ​ J型指令用于无条件跳转到目标地址,常用于函数调用、循环跳转等控制流程的修改。例如,j,jar指令。 对于以上指令,我们只需要记住他们都占32位,4个字节,其他的具体用法详见《MIPS-C指令集》。 ​ 2.32个通用寄存器 ​ MIPS寄存器是32位寄存器,每个部分的功能如图所示 ​ 需要注意的是,每个寄存器既可以用名字表示,也可以用编号表示,如**$to<==>$8**。 3.特殊寄存器 1. HI(high)与LO(low) ​ HI与LO是MIPS中用于处理乘除法的特殊寄存器,在MIPS汇编指令中,乘除法指令的结果最多为64位,夫需要设置特殊寄存器进行保存。在乘法中,HI保存高32位,LO保存低32位;在除法中HI保存余数,LO保存商。 2.PC程序计数器 ​ PC(program counter)程序计数器,是计算机系统中的一个寄存器,用于存储下一条指令的地址。程序计数器指向执行中的指令的内存地址,当处理器执行完当前指令后会自动将程序计数器的值增加,使其指向下一条指令的地址。具体地,在MIPS汇编指令中,每执行完一条指令 PC=PC+4,这是由于在MIPS中每一条指令所占的内存空间都是4个字节。程序计数器的初始值一般为程序的入口地址(首条指令的地址 最常见的为0x0000_3000)。同时分支指令也可以使程序计数器进行跳转。总的来说,PC相当于程序运行中的内存监控,通过PC可以了解程序的流程。 1 2 3 4 5 6 7 8 9 //在我翻阅 MIPS-C指令集时,发现了如下出场率极高的代码 //BEQ: beq rs,rt,offset //描述:if rs==rt then 转移 //以BEQ:相等时转移为例,功能的C语言描述为 if(GPR[rs]==GPR[rt]) PC=PC+4+sign_extend(offset||0^2);//代表在offset后补两位0 即乘四 else PC=PC+4; //我们可以发现,该指令至少会进行一条语句的跳转(四个字节),这也是我查询PC的起始 三.COpre中提供的部分题目具体分析 1.下列指令中需要在立即数后拼接两位0的是 1 答案: beq $s2,$s3,4 在立即数后拼接两位0,即将原立即数向左移动两位,立即数4,代表着按4对齐。在beq中,立即数n的意义是跳转到第n条指令,而实际操作中,一条指令占用四字节,地址访存的话需要跳转4n字节,所以需要拼接两位0。 ...

September 12, 2023 · 2 min · sudo

MIPS基本语法

MIPS代码的基本语法 1. .data数据段 1 2 3 .data fibs: .space 48 size: .word 12 ​ 在数据段进行变量的定义,<变量名> : .<伪指令> <变量内容> 2. .test代码段 ​ 定义程序的代码段,指令后为代码 1 2 .test add $t0,&t1,$t2 3. .macro ​ 使用宏可以避免相同代码多次复用 不带参数定义的宏 macro <macro_name> # # # .end_macro 常用的场景是可以替换程序结束的两行代码 1 2 li $v0,10 syscall 如果我们定义出代表这两行定义的宏 1 2 3 4 macro DONE li $v0,10 syscall .end_macro 这样我们在需要调用这两行代码的时候只需一行 ...

September 12, 2023 · 1 min · sudo

MIPS中的一维数组和二维数组

MIPS中使用数组 1.一维数组 ​ 数组存储需要申请内存空间 .data array: .space 40 # 存储这些数需要用到数组,数组需要使用 10 * 4 = 40 字节 # 一个 int 整数需要占用 4 个字节,需要存储 10 个 int 整数 # 因此,array[0] 的地址为 0x00,array[1] 的地址为 0x04 # array[2] 的地址为 0x08,以此类推。 str: .asciiz "The numbers are:\n" space: .asciiz " " .text li $v0,5 syscall # 输入一个整数 move $s0, $v0 # $s0 is n li $t0, 0 # $t0 循环变量 loop_in: beq $t0, $s0, loop_in_end # $t0 == $s0 的时候跳出循环 li $v0, 5 syscall # 输入一个整数 sll $t1, $t0, 2 # $t1 = $t0 << 2,即 $t1 = $t0 * 4 sw $v0, array($t1) # 把输入的数存入地址为 array + $t1 的内存中 addi $t0, $t0, 1 # $t0 = $t0 + 1 j loop_in # 跳转到 loop_in loop_in_end: la $a0, str li $v0, 4 syscall # 输出提示信息 li $t0, 0 loop_out: beq $t0, $s0, loop_out_end sll $t1, $t0, 2 # $t1 = $t0 << 2,即 $t1 = $t0 * 4 lw $a0, array($t1) # 把内存中地址为 array + $t1 的数取出到 $a0 中 li $v0, 1 syscall # 输出 $a0 la $a0, space li $v0, 4 syscall # 输出一个空格 addi $t0, $t0, 1 j loop_out loop_out_end: li $v0, 10 syscall # 结束程序 2.二维数组 .data matrix: .space 256 # int matrix[8][8] 8*8*4 字节 # matrix[0][0] 的地址为 0x00,matrix[0][1] 的地址为 0x04,…… # matrix[1][0] 的地址为 0x20,matrix[1][1] 的地址为 0x24,…… # …… str_enter: .asciiz "\n" str_space: .asciiz " " # 这里使用了宏,%i 为存储当前行数的寄存器,%j 为存储当前列数的寄存器 # 把 (%i * 8 + %j) * 4 存入 %ans 寄存器中 .macro getindex(%ans, %i, %j) sll %ans, %i, 3 # %ans = %i * 8 add %ans, %ans, %j # %ans = %ans + %j sll %ans, %ans, 2 # %ans = %ans * 4 .end_macro .text li $v0, 5 syscall move $s0, $v0 # 行数 li $v0, 5 syscall move $s1, $v0 # 列数 # 这里使用了循环嵌套 li $t0, 0 # $t0 是一个循环变量 in_i: # 这是外层循环 beq $t0, $s0, in_i_end li $t1, 0 # $t1 是另一个循环变量 in_j: # 这是内层循环 beq $t1, $s1, in_j_end li $v0, 5 syscall # 注意一下下面几行,在 Execute 页面中 Basic 列变成了什么 getindex($t2, $t0, $t1) # 这里使用了宏,就不用写那么多行来算 ($t0 * 8 + $t1) * 4 了 sw $v0, matrix($t2) # matrix[$t0][$t1] = $v0 addi $t1, $t1, 1 j in_j in_j_end: addi $t0, $t0, 1 j in_i in_i_end: # 这里使用了循环嵌套,和输入的时候同理 li $t0, 0 out_i: beq $t0, $s0, out_i_end li $t1, 0 out_j: beq $t1, $s1, out_j_end getindex($t2, $t0, $t1) lw $a0, matrix($t2) # $a0 = matrix[$t0][$t1] li $v0, 1 syscall la $a0, str_space li $v0, 4 syscall # 输出一个空格 addi $t1, $t1, 1 j out_j out_j_end: la $a0, str_enter li $v0, 4 syscall # 输出一个回车 addi $t0, $t0, 1 j out_i out_i_end: li $v0, 10 syscall 事实上,在做矩阵转化一题时,我用了利用一维数组模拟二维数组 ...

September 11, 2023 · 3 min · sudo

git指令的初步学习

面向OO的git指令 ​ 在秋季学期开设的OOpre课程中,我首次接触到使用Gitlab对代码进行版本管理,在第一次OOpre作业中我就受到了提交库的拷打,现在是2023年9月10日晚上22:46,开始编写面向OO的git指令 windows命令行操作经典指令 cd:change dictionary cd D: #只有转移磁盘需要加冒号: cd OO #进一步转移到目录下文件夹 cd .. #回退一层目录 ​ 2.pwd:print working dictionary 打印当前工作目录 ​ 3. git 指令 git是一个终端 提交注意 ​ 在每一次作业的发布中,分为作业发布区和个人仓库区,常见的操作是从作业发布区进行repository的clone,在本地完成作业后push到个人仓库区,需要注意的是公共发布区是没有push权限的,下面以第一次作业为例 完成作业的常见步骤 从公共发布区clone ​ 需要注意的是这里的每一个步骤都可以通过IDEA编译器操作或是通过git终端操作,这里我们主要介绍通过git的操作方式(我更加喜欢)。 ​ 进行源的clone,通过以下代码实现 1 git clone url path ​ 其中url代表远程仓库的地址如SSH密钥,path代表路径,绝对路径或相对路径均可以,如果指定的路径不存在则会创建该目录,笔者经过验证,在路径不存在的情况下,确实会进行生成。 需要注意的是在clone后已经存在.git文件 对代码进行修改后的一系列 常用指令git init git init指令用于生成本地的.git跟踪版本文件,是万恶的开端 git status git status用于查看当前文件的情况,是否被跟踪(tracked 用于代码管理),是否有修改等等 git add git add 指令是很常用的操作,用于将文件添加到tracked,常见的有这几种用法 1 2 3 4 //将.git目录下全体文件加入tracked git add . //添加单个文件进行版本管理 git add <filename>//其中如果filename中包含空格的话需要对文件名进行双引号处理 "filename" 通常对文件进行修改后或新建文件后需要进行git add 这时可以通过git status 进行文件状态的查询 ...

September 10, 2023 · 2 min · sudo