# HBU5_1 **Repository Path**: jeffreysharp/hbu5_1 ## Basic Information - **Project Name**: HBU5_1 - **Description**: 龙芯第5届HBU第一队的代码 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-07-03 - **Last Updated**: 2023-09-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 文档说明 这些代码为2021年河北大学一队的代码 但是之前的代码完全没有注释 因此我们将其添加上我们理解的注释 其中由于ALU部分之前有过汉语注释 因此编码格式为GB2312 其他的注释编码格式应该都为UTF-8 NDSI not delay slot instruction 只要当前的指令是跳转指令 那么它肯定不是延迟槽指令 所以对应到这个指令它的non delay slot instruction信号就一定为1 那么它下一个信号的如果有效的话 必然是延迟槽指令 也就是说 它的 is_delay_slot 信号一定为1 于是我们就可以让当前的non delay slot instruction 信号等待一拍 在下一个时钟周期的时候 把结果写入到 is delay slot 这样一定是有效的 # 命名格式规范 clk rst 其余信号按照如下命名方法 A_B_use_in A_B_use_out A和B为阶段 也就是说明信号必须能够说明是从哪个阶段来的 如果这个信号发送给多个阶段 那么就按照IFA IFB ID EX MA MB WB Monitor 一个一个全部写上 同时是输入信号还是输出信号 中间的use 务必说明清楚内容 尽可能不要采取简写的形式 不采用驼峰命名 驼峰命令对信号来说不够直观 采用CPP的方式 使用_进行分割 模块内部的命名按照 use use_wire 的形式命名 use变量都为寄存器 use_wire为线 # 各阶段信号说明 ## IFA ### 输入信号 clk 1 时钟信号 rst 1 复位信号 ID_IFA_branch_flag_in 1 标记是否分支 ID_IFA_branch_PC_in 32 标记分支跳转到的PC CP_IFA_flush_in 1 从monitor发过来的信号 用于控制是否需要进行异常的处理 跳转到异常处理的入口 CP_IFA_exception_PC_in 32 异常处理跳转的入口 Monitor_IFA_stall_in 1 用于阻塞IFA段 但是这里的阻塞其实也只是用临时寄存器保持了一下值 Monitor_IFA_flush_in 1 用于刷新下一段 其实就是把输出全部置为空了 ### 输出信号 IFA_ICache_fetch_enable_out 1 获取指令地址的写使能 也就是说当前这个信号是否有效 IFA_ICache_PC_out 32 传递给icache的PC值 也就是地址值 IFA_IFB_PC_out 32 传递给IFB的PC用于之后计算EPC 如果需要的话 IFA_IFB_exception_code_out 7 传递给IFB 一直给到CP0计算当前是否发生了异常 IFA段会发生的异常就是地址低两位错误 IFA_IFB_inst_valid_out 1 当前指令是否有效 其实当flush信号有效的时候 这个指令就会是0 ### 模块说明 #### stall branch && exception state machine 用于选择合适的branch和exception PC 因为可能会出现这样的一种情况 当ID段和后面CP0给IFA传递了branch PC 和exception pc 但是IFA这个时候被阻塞了 我们不可能说一直等到IFA没有被阻塞 因为传过来的这些信号可能在下一拍就消失了 所有在这种情况下 我们需要将这些信号暂存 并且使用一个变量,表明我们已经跳转到了暂存这些变量的状态 然后到时候计算PC的时候,就使用这些暂存的变量用于计算 当IFA没有被阻塞的时候,当前的状态变量跳转回之前的状态,同时让用于计算的变量转变为输入的变量 而不是临时的变量 但是由于在这个时钟沿,我就已经在进行计算了 所以此时我拿来计算的变量其实是之前暂存的变量 异常的情况类似,可仿照编写代码 #### PC sel 这里就是为了选择使用哪种方式计算PC 当然计算PC是有优先级的 所以等级最高的是exception 然后是branch 最后才是最正常的PC+4 ## IFB ### 输入信号 clk 1 时钟信号 rst 1 复位信号 IFA_IFB_PC_in 32 由IFA段传递过来的PC IFA_IFB_exception_code_in 7 IFA传递过来的异常编码 IFA_IFB_inst_valid_in 1 IFA传递过来的指令是否有效 ICache_IFB_inst_in 32 从icache得到的指令输入 Monitor_IFB_stall_in 1 monitor发过来的阻塞信号 当阻塞信号生效的时候 数据是不能被写入的 CP_IFB_flush_in 1 CP0发过来的flush信号 当这个信号生效的时候 和IFA段一样 输出的inst valid信号就会无效 ### 输出信号 IFB_ID_PC_out 32 从IFB段给到ID段的信号 只是单纯的信号传递 IFB_ID_inst_out 32 从icache拿到的指令结果 IFB_ID_inst_valid_out 1 当前指令是否是有效指令 IFB_ID_exception_code_out 7 用于CP0判断异常 在IFB中只是简单的数据传递 ### 模块说明 #### inner reg write 单纯就是写一下内部寄存器 不过这里其实是没有写inst指令的 因为inst会涉及到没有icache读stall的问题 #### inst stall state machine 所以这个时候我们需要考虑到指令读取的时候被阻塞的时候 应该如何进行处理 当IFB阶段被阻塞之后 这个时候又从icache拿到的指令 我不能说这个指令我不要了 这样会造成错误 因此我需要将这个指令暂存到inst_T这个临时寄存器中 然后利用一个状态机变量去判别当前到底是处于哪个状态 是应该直接用临时指令寄存器的结果 还是 当前从icache中拿到的指令 这个看的就是stall_in信号是否有效 这里需要注意的是在同一个时序逻辑下 所有的操作都是并行的 这里和我们平常理解的顺序逻辑是不一样的 还需要注意的是状态机变量我们最开始是没有给赋初始值的 所以在拿指令的时候需要防止X Z这种信号的干扰 之后就是信号的输出 ## ID ### 输入信号 clk 1 时钟信号 rst 1 复位信号 CP_ID_flush_in 1 cp0触发异常后 要刷新EX段的数据 所以给ID段的信号了 Monitor_ID_stall_in 1 阻塞当前阶段接收上一段的信息 或者说 让这一段保持之前的信息 Monitor_ID_flush_in 1 刷新EX段的信息 和之前的flush信号用法是一样的 IFB_ID_PC_in 32 PC信号传递 IFB_ID_inst_in 32 需要解码的指令 IFB_ID_inst_valid_in 1 当前指令是否有效 IFB_ID_exception_code_in 7 异常编码的处理 解码阶段会出现的异常是保留指令 当然如果出现了break和system call 也会出现异常 //下面这些信号都是为了处理冲突的 EX_ID_RF_write_enable_in 1 EX段指令对RF的寄存器写使能是否有效 EX_ID_RF_write_addr_in 5 写向哪个寄存器 EX_ID_RF_write_data_in 32 寄存器写数据 EX_ID_load_mem_in 1 EX段的指令是否是lw lb这样的访存指令 EX_ID_mfc0_in 1 EX段的指令是否是mfc0指令 //MA段是不会对这个过程进行操作的 MB_ID_RF_write_enable_in 1 MB段指令对RF的寄存器写使能是否有效 MB_ID_RF_write_addr_in 5 写向哪个寄存器 MB_ID_RF_write_data_in 32 寄存器写数据 WB_ID_RF_write_enable_in 1 WB段指令对RF的寄存器写使能是否有效 WB_ID_RF_write_addr_in 5 写向哪个寄存器 WB_ID_RF_write_data_in 32 寄存器写数据 ### 输出信号 ID_IFA_branch_flag_out 1 是否分支 ID_IFA_branch_PC_out 32 分支的地址 //数据传递 ID_EX_PC_out 32 PC传递 ID_EX_inst_out 32 指令传递 ID_EX_inst_valid_out 1 指令有效传递 ID_EX_exception_code_out 7 异常码传递 ID_EX_op1_out 32 从regfile中读出来的op1 ID_EX_op2_out 32 从regfile中读出来的op2 ID_EX_alu_op_sel_out 5 alu具体进行什么操作 加法还是减法还是啥 ID_EX_hi_write_enable_out 1 hi写使能 ID_EX_lo_write_enable_out 1 lo写使能 ID_EX_hi_write_sel_out 1 选择从op拿数据还是从mul之后从得到的结果拿数据; mthi/mul ID_EX_lo_write_sel_out 1 选择从op拿数据还是从mul之后拿结果 ID_EX_result_sel_out 选择当前指令最后的结果从哪里获得;alu result还是hi还是lo ID_EX_mem_write_enable_out 1 当前这条指令是否需要写存储器 ID_EX_mem_read_enable_out 1 当前这条指令是否需要读存储器 ID_EX_mem_write_sel_out 2 这条指令用于从哪里拿数据; lb mfc0 默认reg file ID_EX_mem_access_mode_out 2 选择是lb lh lw ID_EX_mem_write_data_out 32 选择将哪里的数据写入; 会使用解决冲突之后的op2;代码中叫做lastest_op2 ID_EX_mem_sign_sel_out 1 load指令为lb 还是lbu ID_EX_mfc0_flag_out 1 是否为mfc0指令 ID_EX_non_delay_slot_inst_out 1 当前指令是否为延迟槽指令 ID_EX_RF_write_enable_out 1 传递给EX表明是否要写寄存器 ID_EX_RF_write_addr_out 5 表明需要些的寄存器的地址 ID_EX_CP0_write_enable_out 5 传递给EX段 用于表明之后是否些CP0 ID_EX_CP0_write_addr_out 5 传递给EX段 用于之后表明写CP0的地址 ID_MA_rs_out 5 给MA段判断是否需要阻塞用的 ID_MA_rt_out 5 同上 ID_MA_need_op1_out 1 是否需要op1 ID_MA_need_op2_out 1 是否需要op2 ID_Monitor_stall_out 1 传递给monitor 用于阻塞ID和刷新EX的stall信号 ### 模块说明 #### inner reg write 同样就是简单地写一下内部寄存器 需要注意的是把inst valid这种信号直接进行了传递 #### decode first 我们首先对inst进行初步的解码 这个解码是纯粹逻辑上的 也就是说我们只是把指令上对应位数的opcode rs rt进行了截取 最后把这些结果输出就是了 #### decode second 根据解码第一阶段获得的opcode rs rt func sa 这些生成控制信号 然后这些控制信号有的会去控制是否生成分支跳转的使能信号 当前是否处于延迟槽等等 #### reg file && conflict detect 第一个阶段解码后会把rs rt 给到reg file 然后从其中读出结果后送给冲突检测模块 用于解决前后的数据关联 最后冲突检测模块会把结果送到decode second和op sel模块 送到decode second是因为要在这里直接判别一些条件跳转指令 如bne是否有效 送到op sel则是为了选择合适的操作数给到EX阶段 #### op sel 因为我们在冲突检测阶段虽然是判断了是否需要needop1 和 needop2的 但是为了减少寄存器的使用 我们在ID阶段就直接将结果选择给出去 也就是我们额外通过decode second拿到的信号去选择最后的输出结果是立即数还是op1 #### second delay slot state machine 其实这个叫法很怪异 我说明白一点吧 其实就是延迟槽指令后面的一个指令 这个指令本不应该被执行 但是由于我们有IFA 和 IFB 两段 所以比起传统的5段 我们多取了一个指令 所以这里我们通过插入气泡的方式将这个指令扼杀在ID阶段 然后通过调整状态机的方式来判断哪个时候插入这个气泡 #### branch exec 这个模块主要是用于计算跳转地址的 跳转地址有三种来源 一种是相对的 一种是绝对的 一种是从寄存器里面拿的 相对的我们从指令里面获得 绝对的我们通过就截取等等操作也可以获得 从寄存器里面拿的我们可以让冲突检测后的结果再给一份到当前的branch_exec模块 然后分支是否有效这个信号就会依靠第二个解码阶段来获得了 ## EX ### 输入信号 clk 1 时钟 rst 1 复位 CP_EX_flush_in 1 cp0触发异常后给出的flush信号 Monitor_EX_flush_in 1 monitor给ex的flush信号 用于刷MA Monitor_EX_stall_in 1 monitor给ex的阻塞信号 用于保持ex段的信号 //写吐了 相信这些信号应该很明显 就不写了 ID_EX_PC_in ID_EX_inst_in ID_EX_inst_valid_in ID_EX_exception_code_in ID_EX_op1_in ID_EX_op2_in ID_EX_alu_op_sel_in ID_EX_hi_write_enable_in ID_EX_lo_write_enable_in ID_EX_hi_write_sel_in ID_EX_lo_write_sel_in ID_EX_result_sel_in ID_EX_mem_write_enable_in ID_EX_mem_read_enable_in ID_EX_mem_write_sel_in ID_EX_mem_access_mode_in ID_EX_mem_write_data_in ID_EX_mem_sign_sel_in ID_EX_mfc0_flag_in ID_EX_non_delay_slot_inst_in ID_EX_RF_write_enable_in ID_EX_RF_write_addr_in ID_EX_CP0_write_enable_in ID_EX_CP0_write_addr_in ### 输出信号 //这些是直接传递过去的 只是加了一下stall和flush罢了 EX_MA_PC_out EX_MA_inst_out EX_MA_inst_valid_out EX_MA_exception_code_out //同样是这样的 EX_MA_mem_write_enable_out EX_MA_mem_read_enable_out EX_MA_mem_write_sel_out EX_MA_mem_access_mode_out EX_MA_mem_write_data_out EX_MA_mem_sign_sel_out EX_ID_mfc0_flag_out EX_MA_non_delay_slot_inst_out EX_MA_RF_write_enable_out EX_MA_RF_write_addr_out EX_MA_RF_write_data_out //EX段的计算核心就是这个数据 EX_MA_CP0_write_enable_out EX_MA_CP0_write_addr_out EX_Monitor_stall_out ### 模块说明 #### inner reg write 同样就是写入一下内部寄存器 #### ALU 三个东西 两个操作数 一个操作码 最后输出一个异常和结果 #### MUL & DIV 这两个本来就是两个模块 MUL是调用的IP核 DIV是自己写的一个状态机 在这里算除法 最后它们会把结果写入到一起 生成mulOrDivResult #### hi lo hi和lo有两个输入 一个是从alu 其实这个对应的就是 MTHI这种指令 另外一个输入就是从MUL和DIV获得 但是由于存在着MFHI这种指令 所以HILO还需要一个到RFWdata的路径 #### RFWData 就是判断最后写回到寄存器的值是什么 我们在这里先不管load这种操作 只是从HI和LO还有ALU的结果中选 ## MA ### 输入信号 clk rst Monitor_MA_flush_in Monitor_MA_stall_in EX_MA_PC_in EX_MA_inst_in EX_MA_inst_valid_in EX_MA_exception_code_in EX_MA_RF_write_enable_in EX_MA_RF_write_addr_in EX_MA_RF_write_data_in EX_MA_mem_write_enable_in EX_MA_mem_write_sel_in//最后的结果选择写入到reg file还是写入到mem EX_MA_mem_access_mode_in EX_MA_mem_sign_sel_in EX_MA_mem_write_data_in EX_MA_non_delay_slot_inst_in EX_MA_CP0_write_enable_in EX_MA_CP0_write_addr_in interrupt ID_MA_rs_in ID_MA_rt_in ID_MA_need_op1_in ID_MA_need_op2_in ### 输出信号 //PC和inst的信息传递 MA_MB_PC_out MA_MB_inst_out MA_MB_inst_valid_out //ma段直接把信号给到dcache MA_DCache_mem_write_enable_out MA_DCache_mem_read_enable_out MA_DCache_mem_addr_out MA_DCache_mem_write_data_out //写到哪里 有符号还是无符号 写入到哪几个字节 MA_MB_mem_write_sel_out MA_MB_mem_sign_sel_out MA_MB_mem_byte_sel_out //cp0触发后 修改异常地址 CP_Monitor_flush_out CP_IFA_exception_PC_out //寄存器写使能和相关的数据 MA_MB_RF_write_enable_out MA_MB_RF_write_addr_out MA_MB_RF_write_data_out //ma段因为load这种指令停留的时间 MA_Monitor_stall_out ### 模块说明 #### inner reg write 内部寄存器写 单纯记录一下信息 #### non-delay slot inst trans 因为我们传递的信息是non delay slot inst 但是我们在cp0中用到的是 当前指令是否为延迟槽指令 又已知跳转指令的一定不是延迟槽指令 所以跳转指令的non delay slot inst trans必然为1 那么跳转指令的下一条有效指令必然是延迟槽指令 故只要当前阶段没有被刷新 那么我们就可以通过等待一拍把当前指令的Non-delay slot inst trans给到is delay slot inst #### stall detect 当发现我们要写或者读的和ID阶段给过来的rs和rt冲突了 那么我们就得发出阻塞信号 让这个结果等到MB段再说 #### CP0 就是获得当前可能得到的相关异常 把是不是延迟槽指令给过去 把PC给过去 把中断也给过去 最后将CP0的flush信号给到monitor就是了 #### byte sel && write data sel 在MA段计算一下要最后要写回是怎么写回 half还是byte还是word 当然还有现在的数据怎么写入 ## MB ### 输入信号 clk rst //其实这里是多余的 因为过了MA段 根本用不到这些信号了 MA_MB_PC_in MA_MB_inst_in MA_MB_inst_valid_in //这些是给WB阶段用的 直接传递就是了 MA_MB_RF_write_enable_in MA_MB_RF_write_addr_in MA_MB_RF_write_data_in //从dcache中拿到的数据 DCache_MB_mem_read_data_in //这些是用于对从dcache中拿到的数据进行处理用的 MA_MB_mem_byte_sel_in MA_MB_mem_write_sel_in MA_MB_mem_sign_sel_in ### 输出信号 //其实根本没用 MB_WB_PC_out MB_WB_inst_out //用于寄存器写选择 MB_WB_RF_write_enable_out MB_WB_RF_write_addr_out MB_WB_RF_write_data_out ### 内部模块 #### inner reg write 将从DCache MA段拿来的数据写入到内部的寄存器 #### data sel & RData RFWData 选择是写入alu的结果 然后怎么写 还是写入从地址获得的结果 需要计算是否需要load sign之类的 ## WB ### 输入信号 clk rst //没啥用 debug用 MB_WB_PC_in MB_WB_inst_in //写寄存器的信号 MB_WB_RF_write_enable_in MB_WB_RF_write_addr_in MB_WB_RF_write_data_in ### 输出信号 //给ID用于冲突检测 WB_ID_RF_write_enable_out WB_ID_RF_write_addr_out WB_ID_RF_write_data_out //debug用信号 PC_out inst_out ### 模块说明 #### inner reg write 仅仅只是保存一下结果 然后写入即可 ## 优化思路 ### 平衡流水线 ID段关于branch的计算拿到IFA段来计算 ID段关于op的计算移动到EX段计算完成 将一些无关紧要的if修改为case语句