计算机组成原理(计算机系统3)--实验三:取指和指令译码设计
一、 实验目标:
设计完成一个连续取指令并进行指令译码的电路,从而掌握设计简单数据通路的基本方法。
二、实验内容
本实验完成:1)首先完成一个译码器;2)接着实现一个寄存器文件;3)最后添加指令存储器和地址部件等将这些部件组合成一个数据通路原型。
三、实验环境
硬件:桌面PC
软件:Linux Chisel开发环境
四、实验步骤及说明
1)设计译码电路:
输入位32bit的一个机器字,按照课本MIPS 指令格式,完成add、sub、lw、sw指令译码,其他指令一律译码成nop指令。输入信号名为Instr_word,对上述四条指令义译码输出信号名为add_op、sub_op、lw_op和sw_op,其余指令一律译码为nop,输出信号均为1bit。
给出Chisel设计代码和仿真测试波形,观察输入Instr_word为add R1,R2,R3; sub R0,R5,R6,lw R5,100(R2), sw R5,104(R2)、JAL 100时,对应的输出波形。
(1)指令对象Instructions.scala
指令定义与匹配:使用 BitPat 定义了MIPS指令的匹配模式,这些模式用于在指令解码时识别不同的操作。例如,instr_add 匹配加法指令的位模式。
// 指令对象,定义了MIPS指令及其对应的操作 object Instructions{ // 定义开关状态 val on = 1.U(1.W) val off = 0.U(1.W) // 定义指令的匹配模式 def instr_add = BitPat("b000000???????????????00000100000") def instr_sub = BitPat("b000000???????????????00000100010") def instr_lw = BitPat("b100011??????????????????????????") def instr_sw = BitPat("b101011??????????????????????????") |
操作输出的定义: 定义了不同指令对应的操作输出状态,使用 List 形式表示。例如,add_op 表示加法指令的操作输出,其中的 on 和 off 表示该操作被激活或不被激活。
// 定义每种指令对应的操作输出 val nop_op = List(off,off,off,off,on) val add_op = List(on,off,off,off,off) val sub_op = List(off,on,off,off,off) val lw_op = List(off,off,on,off,off) val sw_op = List(off,off,off,on,off) |
映射关系: 使用 Array 将指令的匹配模式与其对应的操作输出进行了映射。这种设计允许在指令解码时,可以通过匹配指令模式来快速找到对应的操作输出。
// 将指令与其对应的操作输出进行映射 val map = Array( instr_add -> add_op, instr_sub -> sub_op, instr_lw -> lw_op, instr_sw -> sw_op, ) |
Instructions.scala整体代码:
object Instructions{ // 指令对象,定义了MIPS指令及其对应的操作 // 定义开关状态 val on = 1.U(1.W) val off = 0.U(1.W) // 定义指令的匹配模式 def instr_add = BitPat("b000000???????????????00000100000") def instr_sub = BitPat("b000000???????????????00000100010") def instr_lw = BitPat("b100011??????????????????????????") def instr_sw = BitPat("b101011??????????????????????????") // 定义每种指令对应的操作输出 val nop_op = List(off,off,off,off,on) val add_op = List(on,off,off,off,off) val sub_op = List(off,on,off,off,off) val lw_op = List(off,off,on,off,off) val sw_op = List(off,off,off,on,off) // 将指令与其对应的操作输出进行映射 val map = Array( instr_add -> add_op, instr_sub -> sub_op, instr_lw -> lw_op, instr_sw -> sw_op, ) } |
(2)译码器Decoder.scala:
输入输出定义:创建 DecoderSignals 类,定义了输入指令和五个输出信号,分别对应不同的操作。输入为32位的指令,输出为1位的控制信号。
// 定义输入输出信号的Bundle class DecoderSignals extends Bundle{ val Instr_word = Input(UInt(32.W))// 输入指令,32位 // 输出指令 val add_op = Output(UInt(1.W)) val sub_op = Output(UInt(1.W)) val lw_op = Output(UInt(1.W)) val sw_op = Output(UInt(1.W)) val nop_op = Output(UInt(1.W)) } |
Decoder模块:在 Decoder 类中,定义了输入输出接口。使用 ListLookup 函数进行指令解码。该函数根据输入的指令 io.Instr_word 在 map 中查找对应的操作输出信号,默认返回 nop_op 信号。
信号赋值:将 ListLookup 返回的操作信号分别赋值给 io.add_op、io.sub_op、io.lw_op、io.sw_op 和 io.nop_op。这允许后续的电路逻辑根据这些控制信号执行相应的操作。
class Decoder extends Module { val io = IO(new DecoderSignals()) // 使用 ListLookup 进行指令解码,找出对应的操作信号 val decodersignals = ListLookup(io.Instr_word,nop_op,map) // 将解码得到的信号分配给输出 io.add_op := decodersignals(0) io.sub_op := decodersignals(1) io.lw_op := decodersignals(2) io.sw_op := decodersignals(3) io.nop_op := decodersignals(4) } |
Verilog代码生成:exp3_Decoder 对象用于生成Verilog代码,使用 ChiselStage 将 Decoder 模块转换为Verilog代码并保存到指定的目录中。
// 生成Verilog代码的主对象 object exp3_Decoder extends App { (new chisel3.stage.ChiselStage).emitVerilog(new Decoder(), Array("--target-dir","generated")) } |
Decader.scala整体代码:
// 定义输入输出信号的Bundle class DecoderSignals extends Bundle{ val Instr_word = Input(UInt(32.W))// 输入指令,32位 // 输出指令 val add_op = Output(UInt(1.W)) val sub_op = Output(UInt(1.W)) val lw_op = Output(UInt(1.W)) val sw_op = Output(UInt(1.W)) val nop_op = Output(UInt(1.W)) } // 定义解码器 class Decoder extends Module { val io = IO(new DecoderSignals()) // 使用 ListLookup 进行指令解码,找出对应的操作信号 val decodersignals = ListLookup(io.Instr_word,nop_op,map) // 将解码得到的信号分配给输出 io.add_op := decodersignals(0) io.sub_op := decodersignals(1) io.lw_op := decodersignals(2) io.sw_op := decodersignals(3) io.nop_op := decodersignals(4) } // 生成Verilog代码的主对象 object exp3_Decoder extends App { (new chisel3.stage.ChiselStage).emitVerilog(new Decoder(), Array("--target-dir","generated")) } |
(3)译码器测试DecoderTest.scala:
测试框架设置:该测试类继承自 AnyFlatSpec 和 ChiselScalatestTester,使用 Chisel 的测试框架来验证 Decoder 模块的功能。
测试用例描述:使用 behavior of "Decoder" 定义测试的主题,接着使用 it should "pass" 描述具体的测试内容。
class DecoderTest extends AnyFlatSpec with ChiselScalatestTester{ behavior of "Decoder" it should "pass" in{ test(new Decoder).withAnnotations(Seq(WriteVcdAnnotation)){ c=> |
时钟控制:在每次设置新指令后,调用 c.clock.step(1) 来模拟时钟的推进,确保电路能够根据新指令更新状态。
指令测试:每个 poke 调用用于将特定的指令加载到解码器的输入 Instr_word 中。每个指令之后,通过 expect 验证各个操作信号的输出是否符合预期:
NOP 指令: 确认无操作的输出为1。
c.clock.step(1) // 测试无操作指令(NOP) c.io.Instr_word.poke("b00000000_00000000_00000000_00000000".U) c.clock.step(1) |
ADD 指令: 确认加法操作为1,其他操作为0。
// 测试加法指令,设置指令为 add R1, R2, R3 c.io.Instr_word.poke("b000000_00010_00011_00001_00000_100000".U) c.io.add_op.expect(1.U) c.io.lw_op.expect(0.U) c.io.sub_op.expect(0.U) c.io.sw_op.expect(0.U) c.io.nop_op.expect(0.U) c.clock.step(1) |
SUB 指令: 确认减法操作为1,其他操作为0。
// 测试减法指令,设置指令为 sub R0, R5, R6 c.io.Instr_word.poke("b000000_00101_00110_00000_00000_100010".U) c.io.add_op.expect(0.U) c.io.lw_op.expect(0.U) c.io.sub_op.expect(1.U) c.io.sw_op.expect(0.U) c.io.nop_op.expect(0.U) c.clock.step(1) |
LW 指令: 确认加载操作为1,其他操作为0。
// 测试加载指令,设置指令为 lw R5, 100(R2) c.io.Instr_word.poke("b100011_00010_00101_00000_00001_100100".U) c.io.add_op.expect(0.U) c.io.lw_op.expect(1.U) c.io.sub_op.expect(0.U) c.io.sw_op.expect(0.U) c.io.nop_op.expect(0.U) c.clock.step(1) |
SW 指令: 确认存储操作为1,其他操作为0。
// 测试存储指令,设置指令为 sw R5, 104(R2) c.io.Instr_word.poke("b101011_00010_00101_00000_00001_101000".U) c.io.add_op.expect(0.U) c.io.lw_op.expect(0.U) c.io.sub_op.expect(0.U) c.io.sw_op.expect(1.U) c.io.nop_op.expect(0.U) c.clock.step(1) |
DecaderTest.scala整体代码:
class DecoderTest extends AnyFlatSpec with ChiselScalatestTester{ behavior of "Decoder" it should "pass" in{ test(new Decoder).withAnnotations(Seq(WriteVcdAnnotation)){ c=> c.clock.step(1) // 测试无操作指令(NOP) c.io.Instr_word.poke("b00000000_00000000_00000000_00000000".U) c.clock.step(1) // 测试加法指令,设置指令为 add R1, R2, R3 c.io.Instr_word.poke("b000000_00010_00011_00001_00000_100000".U) c.io.add_op.expect(1.U) c.io.lw_op.expect(0.U) c.io.sub_op.expect(0.U) c.io.sw_op.expect(0.U) c.io.nop_op.expect(0.U) c.clock.step(1) // 测试减法指令,设置指令为 sub R0, R5, R6 c.io.Instr_word.poke("b000000_00101_00110_00000_00000_100010".U) c.io.add_op.expect(0.U) c.io.lw_op.expect(0.U) c.io.sub_op.expect(1.U) c.io.sw_op.expect(0.U) c.io.nop_op.expect(0.U) c.clock.step(1) // 测试加载指令,设置指令为 lw R5, 100(R2) c.io.Instr_word.poke("b100011_00010_00101_00000_00001_100100".U) c.io.add_op.expect(0.U) c.io.lw_op.expect(1.U) c.io.sub_op.expect(0.U) c.io.sw_op.expect(0.U) c.io.nop_op.expect(0.U) c.clock.step(1) // 测试存储指令,设置指令为 sw R5, 104(R2) c.io.Instr_word.poke("b101011_00010_00101_00000_00001_101000".U) c.io.add_op.expect(0.U) c.io.lw_op.expect(0.U) c.io.sub_op.expect(0.U) c.io.sw_op.expect(1.U) c.io.nop_op.expect(0.U) c.clock.step(1) // 测试无操作指令,设置指令为 NOP c.io.Instr_word.poke("b000011_00000_00000_00000_00000_000000".U) c.io.add_op.expect(0.U) c.io.lw_op.expect(0.U) c.io.sub_op.expect(0.U) c.io.sw_op.expect(0.U) c.io.nop_op.expect(1.U) c.clock.step(1) println("success!\n") } } } |
2)设计寄存器文件:
共32个32bit寄存器,允许两读一写,且0号寄存器固定读出位0。五个输入信号为RS1、RS2、WB_data、Reg_WB、RF_WrEn,寄存器输出RS1_out和RS2_out;寄存器内部保存的初始数值等同于寄存器编号。
给出Chisel设计代码和仿真测试波形,观察RS1=5,RS2=8,WB_data=0x1234,Reg_WB=1,RF_WrEn=1的输出波形和受影响寄存器的值。
(1)寄存器文件RegisterFile.scala
模块定义:定义一个名为RegisterFile的模块。实现一个包含32个32位寄存器的寄存器文件,支持两读一写的操作。
class RegisterFile extends Module |
输入输出接口定义:
输入信号包括:
RS1:选择第一个要读取的寄存器,5位宽,能够选择0到31的寄存器。
RS2:选择第二个要读取的寄存器,5位宽,能够选择0到31的寄存器。
WB_data:要写入寄存器的数据,32位宽。
Reg_WB:写使能信号,布尔类型,用于控制是否执行写入操作。
输出信号:
RS1_out:第一个寄存器的输出数据,32位宽。
RS2_out:第二个寄存器的输出数据,32位宽。
val io = IO(new Bundle { // RS1,RS2输入信号,用于选择要读取的寄存器 val RS1 = Input(UInt(5.W)) val RS2 = Input(UInt(5.W)) // RS1,RS2输出数据 val RS1_out = Output(UInt(32.W)) val RS2_out = Output(UInt(32.W)) // 写入数据信号,用于写入寄存器 val WB_data = Input(UInt(32.W)) // 选择写入数据的寄存器 val Reg_WB = Input(Bool()) }) |
寄存器数组初始化:使用 RegInit 创建一个寄存器数组 registers,包含32个32位的寄存器,初始值设置为它们的索引(即寄存器编号)。
// 寄存器堆,32个32位寄存器,初始值等于寄存器编号 val registers = RegInit(VecInit((0 until 32).map(_.U(32.W)))) |
读取寄存器:
对于 RS1_out:使用 Mux 判断如果 RS1 为0,输出0;否则,输出对应寄存器的值。
对于 RS2_out:逻辑类似,确保 RS2 为0时输出0,其他情况下输出对应寄存器的值。
// RS1,RS2输出数据,0号寄存器固定读出位0 io.RS1_out := Mux(io.RS1 === 0.U, 0.U, registers(io.RS1)) io.RS2_out := Mux(io.RS2 === 0.U, 0.U, registers(io.RS2)) |
写入寄存器:使用 when 语句检查写使能信号 RF_WrEn 和 Reg_WB。如果 RF_WrEn 为真且 Reg_WB 不为0,则将 WB_data 写入指定的寄存器。特别注意,0号寄存器的写入被禁止,因此在写入时判断 Reg_WB =/= 0.U。
// 写入数据到寄存器 registers(io.Reg_WB) := io.WB_data // 根据写信号选择要写入的寄存器 when (io.Reg_WB) { registers(io.RS1) := Mux(io.RS1 === 0.U, 0.U, io.WB_data) registers(io.RS2) := Mux(io.RS2 === 0.U, 0.U, io.WB_data) } |
该模块的整体功能设计是:该模块实现了一个简单而有效的寄存器文件,允许从任意寄存器读取数据,并在满足条件时向特定寄存器写入数据。需要注意的是要确保读出0号寄存器时输出始终为0。仅在写使能信号为真时执行,并避免向0号寄存器写入。
RegisterFile.scala整体代码:
class RegisterFile extends Module { val io = IO(new Bundle { // RS1,RS2输入信号,用于选择要读取的寄存器 val RS1 = Input(UInt(5.W)) val RS2 = Input(UInt(5.W)) // RS1,RS2输出数据 val RS1_out = Output(UInt(32.W)) val RS2_out = Output(UInt(32.W)) // 写入数据信号,用于写入寄存器 val WB_data = Input(UInt(32.W)) // 选择写入数据的寄存器 val Reg_WB = Input(Bool()) }) // 寄存器堆,32个32位寄存器,初始值等于寄存器编号 val registers = RegInit(VecInit((0 until 32).map(_.U(32.W)))) // 写入数据到寄存器 registers(io.Reg_WB) := io.WB_data // RS1,RS2输出数据,0号寄存器固定读出位0 io.RS1_out := Mux(io.RS1 === 0.U, 0.U, registers(io.RS1)) io.RS2_out := Mux(io.RS2 === 0.U, 0.U, registers(io.RS2)) // 根据写信号选择要写入的寄存器 when (io.Reg_WB) { registers(io.RS1) := Mux(io.RS1 === 0.U, 0.U, io.WB_data) registers(io.RS2) := Mux(io.RS2 === 0.U, 0.U, io.WB_data) } } object RegisterFile extends App { (new chisel3.stage.ChiselStage).emitVerilog(new RegisterFile(), Array("--target-dir","generated")) } |
(2)寄存器文件测试RegisterFileTest.scala:
测试类:RegisterFileTest 继承自 AnyFlatSpec 和 ChiselScalatestTester,用于编写测试。
class RegisterFileTest extends AnyFlatSpec with ChiselScalatestTester |
测试模块行为:使用 behavior of "RegisterFile Module" 定义被测试的模块的名称。it should "observe the effect on registers" 定义测试的目的,即观察寄存器的效果。
behavior of "RegisterFile Module" it should "observe the effect on registers" in |
测试过程:使用 test 方法创建一个 RegisterFile 实例,并启用 VCD 波形输出(通过 WriteVcdAnnotation)。在测试块内部,使用 poke 方法设置输入信号。
时钟步进:c.clock.step(1)让时钟向前迈进一步,这将触发 RegisterFile 模块的行为,执行写操作。
test (new RegisterFile).withAnnotations(Seq(WriteVcdAnnotation)) { c => // 设置输入信号 c.io.RS1.poke(5.U) // RS1 = 5 c.io.RS2.poke(8.U) // RS2 = 8 c.io.WB_data.poke(0x1234.U) // WB_data = 0x1234 // 时钟向前一步, 写入 c.io.Reg_WB.poke(true) // 写使能 c.clock.step(1) } |
RegisterFileTest.scala整体代码:
class RegisterFileTest extends AnyFlatSpec with ChiselScalatestTester { behavior of "RegisterFile Module" it should "observe the effect on registers" in { test (new RegisterFile).withAnnotations(Seq(WriteVcdAnnotation)) { c => // 设置输入信号 c.io.RS1.poke(5.U) // RS1 = 5 c.io.RS2.poke(8.U) // RS2 = 8 c.io.WB_data.poke(0x1234.U) // WB_data = 0x1234 // 时钟向前一步, 写入 c.io.Reg_WB.poke(true) // 写使能 c.clock.step(1) } } } |
3)设计指令存储器:
实现一个32个字的指令存储器,从0地址分别存储4条指令add R1,R2,R3; sub R0,R5,R6,lw R5,100(R2), sw R5,104(R2)。然后组合指令存储器、寄存器文件、译码电路,并结合PC更新电路(PC初值为0),最终让电路能逐条指令取出、译码(不需要完成指令执行)。给出Chisel设计代码和仿真测试波形,观察四条指令的执行过程波形,记录并解释其含义。
(1)MemAndPC模块(指令存储与指令计数)
模块定义:定义一个名为MemAndPC的模块。实现一个基本的内存读写模块,支持指令的写入和读取。
class MemAndPC extends Module |
输入输出接口定义:
输入信号:
rdEna:读取使能信号,布尔类型,控制是否执行读取操作。
wrEna:写入使能信号,布尔类型,控制是否执行写入操作。
wrAddr:写入地址,10位宽,用于指定要写入的内存地址。
wrData:要写入的数据,32位宽。
输出信号:rdData:读取的数据,32位宽。
val io = IO(new Bundle { // 读 val rdEna = Input(Bool()) val rdData = Output(UInt(32.W)) // 写 val wrEna = Input(Bool()) val wrAddr = Input(UInt(10.W)) val wrData = Input(UInt(32.W)) }) |
(2)寄存器和内存初始化:
程序计数器(PC):pcReg初始化为0的32位寄存器,用作当前读取的内存地址。
内存:使用 SyncReadMem 定义一个大小为128字节的同步读取内存,每个单元为8位(1字节)。
val pcReg = RegInit(0.U(32.W)) val mem = SyncReadMem(128,UInt(8.W)) |
写入内存:当 wrEna 为真时,执行写入操作。写入数据时,将32位的 wrData 分成4个8位的部分,依次写入到指定地址 wrAddr 及其后续地址(wrAddr + 1、wrAddr + 2 和 wrAddr + 3)。
// 写内存 when (io.wrEna) { // 分 4 个单元写 mem.write(io.wrAddr, io.wrData(7, 0)) mem.write(io.wrAddr + 1.U, io.wrData(15, 8)) mem.write(io.wrAddr + 2.U, io.wrData(23, 16)) mem.write(io.wrAddr + 3.U, io.wrData(31, 24)) } |
读取内存:当 rdEna 为真时,执行读取操作。读取4个连续的内存单元,分别从 pcReg、pcReg + 1、pcReg + 2 和 pcReg + 3 读取数据。使用连接运算符 ## 将四个读取的数据拼接成一个32位的 rdData 输出。读取后,pcReg 增加4,指向下一组指令。如果 rdEna 为假,rdData 输出为0(默认输出)。
// 读内存 when (io.rdEna) { // 分 4 个单元读 val rdData0 = mem.read(pcReg) val rdData1 = mem.read(pcReg+1.U) val rdData2 = mem.read(pcReg+2.U) val rdData3 = mem.read(pcReg+3.U) io.rdData := rdData3 ## rdData2 ## rdData1 ## rdData0 // ## 表连接 pcReg := pcReg + 4.U }.otherwise{ io.rdData := 0.U } |
该模块的整体功能设计是:
- 该模块实现了一个简单的指令存储器,能够支持对指令的写入和读取,并且自动更新程序计数器以便于顺序读取。
- 通过分块读取和写入,支持32位数据在内存中的分布。
- 程序计数器PC的增加使得模块可以连续读取指令,适合顺序执行的指令流。
整体代码:
class MemAndPC extends Module { val io = IO(new Bundle { // 读 val rdEna = Input(Bool()) val rdData = Output(UInt(32.W)) // 写 val wrEna = Input(Bool()) val wrAddr = Input(UInt(10.W)) val wrData = Input(UInt(32.W)) }) val pcReg = RegInit(0.U(32.W)) val mem = SyncReadMem(128,UInt(8.W)) // 写内存 when (io.wrEna) { // 分 4 个单元写 mem.write(io.wrAddr, io.wrData(7, 0)) mem.write(io.wrAddr + 1.U, io.wrData(15, 8)) mem.write(io.wrAddr + 2.U, io.wrData(23, 16)) mem.write(io.wrAddr + 3.U, io.wrData(31, 24)) } // 读内存 when (io.rdEna) { // 分 4 个单元读 val rdData0 = mem.read(pcReg) val rdData1 = mem.read(pcReg+1.U) val rdData2 = mem.read(pcReg+2.U) val rdData3 = mem.read(pcReg+3.U) io.rdData := rdData3 ## rdData2 ## rdData1 ## rdData0 // ## 表连接 pcReg := pcReg + 4.U }.otherwise{ io.rdData := 0.U } } |
4)Junction模块(综合电路)
模块定义:定义一个名为Junction的模块。该模块整合了指令解码、寄存器文件和指令内存,形成一个简单的处理器结构。
class Junction extends Module |
输入输出接口定义:
输出信号:
Instr_word:输出当前指令的32位数据。
add_op、sub_op、lw_op、sw_op、nop_op:指令操作信号,布尔类型,用于指示当前指令的类型。
RS1_out、RS2_out:寄存器文件中读取的两个寄存器的输出数据。
输入信号:
rdEna:读取使能信号,控制是否执行写入操作。
wrAddr:写入地址,10位宽,用于指定要写入的内存地址。
wrData:要写入的数据,32位宽。
wrEna:写入使能信号,控制是否执行写入操作。
val io = IO(new Bundle { // Decoder val Instr_word = Output(UInt(32.W)) val add_op = Output(Bool()) val sub_op = Output(Bool()) val lw_op = Output(Bool()) val sw_op = Output(Bool()) val nop_op = Output(Bool()) // RegisterFile val RS1_out = Output(UInt(32.W)) val RS2_out = Output(UInt(32.W)) // Instruction val rdEna = Input(Bool()) val wrAddr = Input(UInt(10.W)) val wrData = Input(UInt(32.W)) val wrEna = Input(Bool()) }) |
模块实例化:
- 解码器:decoder实例化一个解码器模块,负责解码指令并生成操作信号。
- 寄存器文件:registerFile实例化寄存器文件模块,负责存储和读取寄存器的数据。
- 指令内存:instructionMemory实例化指令存储模块,负责存储和读取指令。
// 实例化 val decoder = Module(new Decoder) val registerFile = Module(new RegisterFile) val instructionMemory = Module(new Instruction) |
连接信号:
- 指令操作信号连接:从解码器输出的操作信号连接到模块输出。
- 控制信号连接:将读取和写入信号连接到指令内存模块,以控制其操作。
- 输入数据连接:从指令内存读取的数据用于选择寄存器文件中的寄存器(RS1 和 RS2),通过指令的特定位提取寄存器编号。
- 指令连接:将指令内存的读取数据直接传递给解码器进行处理。
// 连接指令信号 io.add_op := decoder.io.add_op io.sub_op := decoder.io.sub_op io.lw_op := decoder.io.lw_op io.sw_op := decoder.io.sw_op io.nop_op := decoder.io.nop_op // 连接控制信号 instructionMemory.io.rdEna := io.rdEna instructionMemory.io.wrEna := io.wrEna instructionMemory.io.wrAddr := io.wrAddr instructionMemory.io.wrData := io.wrData // 连接输入数据 registerFile.io.RS1 := instructionMemory.io.rdData(25,21) registerFile.io.RS2 := instructionMemory.io.rdData(20, 16) decoder.io.Instr_word := instructionMemory.io.rdData io.Instr_word := instructionMemory.io.rdData |
初始化寄存器文件控制信号:初始化 Reg_WB 和 WB_data 为默认值,表示在当前模块内不进行寄存器的写入操作。
// 初始化寄存器文件的控制信号 registerFile.io.Reg_WB := false.B registerFile.io.WB_data := 0.U |
输出信号连接:将寄存器文件的输出信号直接连接到模块的输出,以便外部模块访问。
// 连接模块输出信号 io.RS1_out := registerFile.io.RS1_out io.RS2_out := registerFile.io.RS2_out |
该模块的整体功能设计是:
- 模块整合了指令存储、寄存器访问和指令解码的基本功能,为一个简单的处理器提供了核心组件。
- 模块之间通过输入输出信号进行连接,形成数据流,简化了指令的处理流程。
- 通过将不同的功能模块化,便于后续扩展和维护。
整体代码:
class Junction extends Module { val io = IO(new Bundle { // Decoder val Instr_word = Output(UInt(32.W)) val add_op = Output(Bool()) val sub_op = Output(Bool()) val lw_op = Output(Bool()) val sw_op = Output(Bool()) val nop_op = Output(Bool()) // RegisterFile val RS1_out = Output(UInt(32.W)) val RS2_out = Output(UInt(32.W)) // Instruction val rdEna = Input(Bool()) val wrAddr = Input(UInt(10.W)) val wrData = Input(UInt(32.W)) val wrEna = Input(Bool()) }) // 实例化 val decoder = Module(new Decoder) val registerFile = Module(new RegisterFile) val instructionMemory = Module(new Memory) // 连接指令信号 io.add_op := decoder.io.add_op io.sub_op := decoder.io.sub_op io.lw_op := decoder.io.lw_op io.sw_op := decoder.io.sw_op io.nop_op := decoder.io.nop_op // 连接控制信号 instructionMemory.io.rdEna := io.rdEna instructionMemory.io.wrEna := io.wrEna instructionMemory.io.wrAddr := io.wrAddr instructionMemory.io.wrData := io.wrData // 连接输入数据 registerFile.io.RS1 := instructionMemory.io.rdData(25,21) registerFile.io.RS2 := instructionMemory.io.rdData(20, 16) decoder.io.Instr_word := instructionMemory.io.rdData io.Instr_word := instructionMemory.io.rdData // 初始化寄存器文件的控制信号 registerFile.io.Reg_WB := false.B registerFile.io.WB_data := 0.U // 连接模块输出信号 io.RS1_out := registerFile.io.RS1_out io.RS2_out := registerFile.io.RS2_out } |
5)仿真测试波形:
测试类:JunctionTest 类继承自 AnyFlatSpec,并实现 ChiselScalatestTester,用于编写测试案例。
class JunctionTest extends AnyFlatSpec with ChiselScalatestTester |
测试行为描述:使用 behavior of "Junction" 描述要测试的模块,指定模块名为 "Junction"。it should "pass" 定义了一个测试用例,表示期望该测试能够成功通过。
behavior of "Junction" it should "pass" in { |
测试逻辑:使用 test(new Junction) 来实例化 Junction 模块,并使用 withAnnotations(Seq(WriteVcdAnnotation)) 记录波形。
test(new Junction).withAnnotations(Seq(WriteVcdAnnotation)) { c => |
指令定义:定义五条 MIPS 指令,以 32 位无符号整数形式表示(即二进制格式)。
ADD: add R1, R2, R3(十六进制为 00430820)
SUB: sub R0, R5, R6(十六进制为 00A60022)
LW: lw R5, 100(R2)(十六进制为 8CA20032)
SW: sw R5, 104(R2)(十六进制为 ACA20034)
JAL: jal 100(十六进制为 0C220032)
val ADD = "b00000000010000110000100000100000".U(32.W) // add R1, R2, R3 val SUB = "b00000000101001100000000000100010".U(32.W) // sub R0, R5, R6 val LW = "b10001100101000100000000000110010".U(32.W) // lw R5, 100(R2) val SW = "b10101100101000100000000000110100".U(32.W) // sw R5, 104(R2) val JAL = "b00001100001000100000000000110010".U(32.W) // jal 100 |
时钟设置: c.clock.setTimeout(0) 初始化时钟,设置超时为 0,表示不限制时钟步骤。
写入指令:对每条指令进行写入操作:
通过 poke 方法设置 wrEna 为 true 以使能写入。
使用 wrAddr 指定写入地址(从 0 到 16,每条指令占 4 字节)。
使用 wrData 将指令数据写入内存。
每次写入后,调用 c.clock.step(1) 使时钟前进一步。
// ADD 指令 c.io.wrEna.poke(true) c.io.wrAddr.poke(0.U) c.io.wrData.poke(ADD) c.clock.step(1) |
结束写入:在写完所有指令后,将 wrEna 设为 false,表示结束写入。使能读操作,通过 poke 将 rdEna 设置为 true,准备读取存储器中的指令。
结束测试:c.clock.step(10) 让时钟继续前进,保持一定时间,以便可以观察到输出波形。
// 结束 c.io.wrEna.poke(false) c.io.rdEna.poke(true) c.clock.step(10) |
仿真波形观察:观察写入指令到存储器的过程,每条指令的地址和数据应在时序上相应。观察add R1,R2,R3; sub R0,R5,R6,lw R5,100(R2), sw R5,104(R2)四条指令的执行过程波形,以及观察寄存器输出,以验证寄存器文件是否能正确根据指令提取寄存器的值。
整体测试代码:
class JunctionTest extends AnyFlatSpec with ChiselScalatestTester { behavior of "Junction" it should "pass" in { test(new Junction).withAnnotations(Seq(WriteVcdAnnotation)) { c => val ADD = "b00000000010000110000100000100000".U(32.W) // add R1, R2, R3 的十六进制为 00430820 val SUB = "b00000000101001100000000000100010".U(32.W) // sub R0, R5, R6 的十六进制为 00A60022 val LW = "b10001100101000100000000000110010".U(32.W) // lw R5, 100(R2) 的十六进制为 8CA20032 val SW = "b10101100101000100000000000110100".U(32.W) // sw R5, 104(R2) 的十六进制为 ACA20034 val JAL = "b00001100001000100000000000110010".U(32.W) // jal 100 的十六进制为 0C220032 c.clock.setTimeout(0) // 初始化时钟 c.io.wrEna.poke(true) // ADD 指令 c.io.wrAddr.poke(0.U) c.io.wrData.poke(ADD) c.clock.step(1) c.io.wrEna.poke(true) // SUB 指令 c.io.wrAddr.poke(4.U) c.io.wrData.poke(SUB) c.clock.step(1) c.io.wrEna.poke(true) // LW 指令 c.io.wrAddr.poke(8.U) c.io.wrData.poke(LW) c.clock.step(1) c.io.wrEna.poke(true) // SW 指令 c.io.wrAddr.poke(12.U) c.io.wrData.poke(SW) c.clock.step(1) c.io.wrEna.poke(true) // JAL 指令 c.io.wrAddr.poke(16.U) c.io.wrData.poke(JAL) c.clock.step(1) c.io.wrEna.poke(false) // 结束 c.io.rdEna.poke(true) c.clock.step(10) } } } |
相关文章:
计算机组成原理(计算机系统3)--实验三:取指和指令译码设计
一、 实验目标: 设计完成一个连续取指令并进行指令译码的电路,从而掌握设计简单数据通路的基本方法。 二、实验内容 本实验完成:1)首先完成一个译码器;2)接着实现一个寄存器文件;3࿰…...
紫光无人机AI飞控平台介绍
随着无人机技术的迅猛发展,无人机飞控平台的智能化需求不断提升。紫光无人机AI飞控平台作为一款创新型产品,为用户提供了从飞行控制到任务管理的一站式解决方案,尤其在AI实时识别和事件分析方面具有显著优势。本文将介绍平台的核心功能、技术…...
基于EMQX+MQTT+ESP32+Openharmony的开发实例
EMQX介绍 EMQ X 是基于 Erlang/OTP 平台开发的 MQTT 消息服务器,是开源社区中最流行的 MQTT 消息服务器。EMQX 是开源百万级分布式 MQTT 消息服务器(MQTT Messaging Broker),用于支持各种接入标准 MQTT 协议的设备,实…...
npm发布组件(vue3+webpack)
1.初始化Vue项目 vue create my-app 2.本地运行 npm run serve 3.新增目录和文件 1. src/package/index.js 2. src/package/wlz-btn/index.vue 3. src/package/wlz-input/index.vue // src\package\index.js import WlzBtn from "./wlz-btn"; import WlzInput …...
kubuntu24.04配置vmware17.5.1
背景 个人主机的最后一次折腾吧。 丝滑上网前提:singbox实践https://blog.csdn.net/qq_43652666/article/details/145190110 vmware 博通官网下载vmware workstation pro 17.5.1版本,注意一个新注册的账号只能下载一个win版本的vmware和一个linux版本…...
开发规范
开发规范 企业项目开发有2种开发模式:前后台混合开发和前后台分离开发。 前后台混合开发 顾名思义就是前台后台代码混在一起开发,如下图所示: 这种开发模式有如下缺点: 沟通成本高:后台人员发现前端有问题…...
大模型UI:Gradio全解11——Chatbot:融合大模型的聊天机器人(4)
大模型UI:Gradio全解11——Chatbot:融合大模型的聊天机器人(4) 前言本篇摘要11. Chatbot:融合大模型的多模态聊天机器人11.4 使用Blocks创建自定义聊天机器人11.4.1 简单聊天机器人演示11.4.2 流式传输Chatbot11.4.3 添…...
[操作系统] 深入理解操作系统的概念及定位
概念 任何计算机系统都包含⼀个基本的程序集合,称为操作系统(OS)。 其核心功能如图片所示,包括: 内核 (Kernel): 内核是操作系统的核心部分,被认为是狭义上的操作系统,直接与硬件打交道。负责进程管理、内…...
redhat安装docker 24.0.7
1、下载docker镜像包 wget https://download.docker.com/linux/static/stable/x86_64/docker-24.0.7.tgz 2、解压 tar -xvf docker-24.0.7.tgz 3、解压的docker文件夹全部移动至/usr/bin目录 cd docker cp -p docker/* /usr/bin 4、注册服务 vi /usr/lib/systemd/syste…...
数据库基础实验1(创建表,设置外键,检查,不为空,主键等约束)安装mysql详细步骤
安装MySQL详细步骤 1. 下载 MySQL 安装程序 访问 MySQL 官方网站:MySQL Downloads。在下载页面,选择 "MySQL Community (GPL) Downloads"。在 "MySQL Community Server" 部分,根据你的操作系统(Windows&…...
如何攻击一个服务器(仅用于教育及娱乐实验目的)
import socket import osdef create_virus():# 创建一个简单的病毒脚本,它会不断尝试连接目标服务器并发送恶意数据virus_code """ import socket import time import threadingdef attack_server(ip, port):while True:try:s socket.socket(socke…...
HarmonyOS NEXT应用开发边学边玩系列:从零实现一影视APP (二、首页轮播图懒加载的实现)
在开发一款影视APP时,首页的轮播图是一个非常重要的部分。它不仅能够吸引用户的注意力,还能有效地推广重点内容。为了提升应用的性能和用户体验,可以实现轮播图的懒加载功能。本文将详细介绍如何在HarmonyOS NEXT应用开发中实现这一功能。 1.…...
Jmeter 简单使用、生成测试报告(一)
一、下载Jmter 去官网下载,我下载的是apache-jmeter-5.6.3.zip,解压后就能用。 二、安装java环境 JMeter是基于Java开发的,运行JMeter需要Java环境。 1.下载JDK、安装Jdk 2.配置java环境变量 3.验证安装是否成功(java -versio…...
采用海豚调度器+Doris开发数仓保姆级教程(满满是踩坑干货细节,持续更新)
目录 一、采用海豚调度器+Doris开发平替CDH Hdfs + Yarn + Hive + Oozie的理由。 1. 架构复杂性 2. 数据处理性能 3. 数据同步与更新 4. 资源利用率与成本 6. 生态系统与兼容性 7. 符合信创或国产化要求 二、ODS层接入数据 接入kafka实时数据 踩坑的问题细节 三、海…...
大疆最新款无人机发布,可照亮百米之外目标
近日,DJI 大疆发布全新小型智能多光旗舰 DJI Matrice 4 系列,包含 Matrice 4T 和 Matrice 4E 两款机型。DJI Matrice 4E 价格为27888 元起,DJI Matrice 4T价格为38888元起。 图片来源:大疆官网 DJI Matrice 4E DJI Matrice 4T D…...
无公网IP 实现外网访问本地 Docker 部署 Navidrome
Navidrome 是一款可以在 macOS、Linux、Windows以及 Docker 等平台上运行的跨平台开源音乐服务器应用,它支持传输常见的 MP3、FLAC、WAV等音频格式。允许用户通过 Web 界面或 API 进行音乐库的管理和访问。本文就介绍如何快速在 Linux 系统使用 Docker 进行本地部署…...
踏上 C++ 编程之旅:开篇之作
踏上 C 编程之旅:开篇之作 在计算机编程的广袤天地中,C 宛如一座巍峨的高峰,吸引着无数开发者攀登探索。今天,就让我们一同开启这段充满挑战与惊喜的 C 编程之旅,在代码的世界里开辟属于自己的道路。 一、为什么选择…...
JS逆向系列之某考古站数据解密
声明 本文章中所有内容仅供学习交流,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请私信我立即删除! 文章目录 声明本次目标网址逆向分析ing代码python实现最近太忙了,博客摆烂了好久,狗头保命.jpg。 本次目标网址 aHR0cHM6Ly93d3cua2F…...
idea 如何安装 github copilot
idea 如何安装 github copilot 要在 IntelliJ IDEA 中安装 GitHub Copilot,可以按照以下步骤操作: 打开 IntelliJ IDEA: 启动 IntelliJ IDEA。 打开插件管理器: 点击菜单栏中的 File。 选择 Settings(Windows/Linux)或 Prefere…...
html5各行各业官网模板源码下载 (4)
文章目录 1.来源2.源码模板2.1 html实现酷炫美观的可视化大屏(十种风格示例,附源码)2.2 HTML5实现古典音乐网站源码模板22.3 HTML5实现古典音乐网站源码模板32.4 HTML5实现小鸟过管道小游戏源码2.5 HTML5实现俄罗斯方块小游戏2.5 HTML5实现剪刀石头布小游戏(附源码)…...
2023-2024 学年 广东省职业院校技能大赛(高职组)“信息安全管理与评估”赛题一
2023-2024 学年 广东省职业院校技能大赛(高职组“信息安全管理与评估”赛题一) 模块一:网络平台搭建与设备安全防护第一阶段任务书任务 1:网络平台搭建任务 2:网络安全设备配置与防护DCRS:DCFW:DCWS:DCBC:WAF: 模块二:网络安全事件…...
理解CPU负载与使用率
目录 CPU使用率 CPU负载 CPU使用率 定义:就像看一个工人干活的时间占他上班时间的比例。比如工人上班8小时,实际干活6小时,干活时间占比就是68100%75%。对于CPU,单核的看它被占用的时间占总时间的比例,多核的就把每个…...
鸿蒙-点击Notification通知并打开App的具体页面
意图通知 获取router事件中传递参数并跳转 目前点击通知消息打开应用的指定页面,通过为通知添加行为意图的方式。也就是在wants的parameters中设置自定义参数,然后在UIAbility的onNewWant或者onCreate方法中 解析配置的自定义参数信息判断跳转不同页面&a…...
Jmeter分布式测试的注意事项和常见问题
🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 Jmeter是一款开源的性能测试工具,使用Jmeter进行分布式测试时,也需要注意一些细节和问题,否则可能会影响测试结果的准确性和可靠…...
w~Transformer~合集11
我自己的原文哦~ https://blog.51cto.com/whaosoft/12472192 #LightSeq 最高加速9倍!字节跳动开源8比特混合精度Transformer引擎,近年来,Transformer 已经成为了 NLP 和 CV 等领域的主流模型,但庞大的模型参数限制了它的高效训练和推理。…...
YOLOv10-1.1部分代码阅读笔记-build.py
build.py ultralytics\data\build.py 目录 build.py 1.所需的库和模块 2.class InfiniteDataLoader(dataloader.DataLoader): 3.class _RepeatSampler: 4.def seed_worker(worker_id): 5.def build_yolo_dataset(cfg, img_path, batch, data, mode"train"…...
c++ 中的容器 vector、deque 和 list 的区别
表格汇总: 容器存储结构随机访问性能中间插入/删除性能两端插入/删除性能内存管理特点迭代器类型适用场景vector连续存储的动态数组 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n)(需要移动元素)末尾: O ( 1 ) O(1) O(1),头部…...
如何有效学习PyTorch:从基础到实践的全面指南
随着人工智能和深度学习技术的飞速发展,PyTorch作为当前最流行的深度学习框架之一,凭借其动态计算图、灵活的编程模型以及强大的社区支持,在学术界和工业界均得到了广泛应用。本文旨在为初学者和有一定基础的读者提供一套系统、全面的PyTorch…...
大模型WebUI:Gradio全解11——Chatbots:融合大模型的多模态聊天机器人(3)
大模型WebUI:Gradio全解11——Chatbot:融合大模型的多模态聊天机器人(3) 前言本篇摘要11. Chatbot:融合大模型的多模态聊天机器人11.3 组件Chatbot及ChatMessage11.3.1 Chatbot:聊天机器人组件1. API参数2.…...
MTK6768 Android13 亮度条均匀调节实现
文章目录 需求:问题现象:需求实现疑难问题点:相关资源修改的文件调试技巧具体需求实现去除亮度弹框设置去掉跳转逻辑SystemUI亮度条长按跳转屏蔽 实现亮度均匀调节PhoneWindowManager.javaBrightnessUtils convertLinearToGammaFloatBrightne…...
力扣560和为K的数组
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。 示例 1: 输入:nums [1,1,1], k 2 输出:2示例 2: 输入:nums [1,2,3], …...
【MVCC过程中会加锁吗?】
MVCC过程中会加锁吗? 一、MVCC的工作原理二、MVCC的并发控制三、MVCC中的加锁情况在MVCC(Multi-Version Concurrency Control,多版本并发控制)过程中, 通常不需要加锁来控制并发访问。 MVCC是一种数据并发控制技术,它允许在不同的事务中对同一数据进行并发访问,而不需要…...
LeetCode100之搜索二维矩阵(46)--Java
1.问题描述 给你一个满足下述两条属性的 m x n 整数矩阵: 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回…...
Android BottomNavigationView不加icon使text垂直居中,完美解决。
这个问题网上千篇一律的设置iconsize为0,labale固定什么的,都没有效果。我的这个基本上所有人用都会有效果。 问题解决之前的效果:垂直方向,文本不居中,看着很难受 问题解决之后:舒服多了 其实很简单&…...
cmake + vscode + mingw 开发环境配置
1.软件准备 准备如下软件: mingw64(安装完成之后检测是否有环境变量,如果没有需要配置) cmake(安装完成之后检测是否有环境变量,如果没有需要配置) vscode(安装CMake插件࿰…...
【GPON实战】ONT和OLT的vlan处理机制(一)
引言 ONT和OLT支持多种vlan配置,包括单层tag vlan,默认vlan(PVID),vlan转换,vlan翻译,双层vlan等等。那ONT和OLT是如何处理的呢?本文将介绍ONT和OLT对vlan的处理机制,第一篇介绍单层vlan场景ONT和OLT如何打vlan和剥离vlan,第二篇将介绍OLT是如何通过omci消息将vlan的…...
Qt模块概览(核心模块、GUI模块等)
Qt 模块概览 Qt 是一个跨平台的应用程序开发框架,广泛用于开发图形用户界面(GUI)程序,在前面的章节中,我们已经介绍了许多控件、布局的用法,这些都属于QT的GUI模块,当然QT也支持非 GUI 程序的开发,也就是核心模块。 Qt 框架由多个模块组成,每个模块提供特定的功能。…...
七大排序算法
文章目录 排序的概念及引用1.插入排序2.希尔排序(缩小增量排序)3.选择排序4.堆排序5.冒泡排序6.快速排序7.归并排序8.代码排序部分的测试9.代码加效果大致测试时间(仅供参考) 排序的概念及引用 排序:将数据按照特定的规律排成递增或递减的操作 稳定性:…...
代理模式实现
一、概念:代理模式属于结构型设计模式。客户端不能直接访问一个对象,可以通过代理的第三者来间接访问该对象,代理对象控制着对于原对象的访问,并允许在客户端访问对象的前后进行一些扩展和处理;这种设置模式称为代理模…...
国产linux系统(银河麒麟,统信uos)使用 PageOffice 实现后台批量生成PDF文档
PageOffice 国产版 :支持信创系统,支持银河麒麟V10和统信UOS,支持X86(intel、兆芯、海光等)、ARM(飞腾、鲲鹏、麒麟等)、龙芯(LoogArch)芯片架构。 PageOffice 版本&…...
基于若依的脚手架,扩展了flowable、mybatisPlus、lombok、前端美化
前言 若依框架可以说是非常优秀的框架,奈何前端一直有点丑,而且集成的东西比较少,我就基于若依,做了一个轻美化版本,主要集成了工作流、mybatisPlus、lombok等工具。 因为我也在用这个框架为公司做系统,所…...
LeetCode 热题 100 | 矩阵
矩阵基础 使用哈希数组来标记当前行或者列是否出现0按层模拟 73. 矩阵置零 题目讲解:LeetCode 重点: 使用标记数组:用两个标记数组分别记录每一行和每一列是否有零出现。使用两个标记变量:用矩阵的第一行和第一列代替两个标记数组…...
(经过验证)在 Ubuntu 系统中为 VSCode、PyCharm 终端及 Jupyter Notebook 配置代理的完整方案
文章目录 1. 通过系统环境变量配置代理步骤一:打开终端步骤二:编辑 ~/.bashrc 文件步骤三:添加代理环境变量步骤四:保存并关闭文件步骤五:使配置生效步骤六:重启相关应用步骤七:使用代理函数 2.…...
【Linux】sed编辑器二
一、处理多行命令 sed编辑器有3种可用于处理多行文本的特殊命令。 N:加入数据流中的下一行,创建一个多行组进行处理;D:删除多行组中的一行;P:打印多行组中的一行。 1、next命令:N 单行next命…...
STM32 FreeRTOS移植
目录 FreeRTOS源码结构介绍 获取源码 1、 官网下载 2、 Github下载 源码结构介绍 源码整体结构 FreeRTOS文件夹结构 Source文件夹结构如下 portable文件夹结构 RVDS文件夹 MemMang文件夹 FreeRTOS在基于寄存器项目中移植步骤 目录添加源码文件 工程添加源码文件 …...
python 寻找数据拐点
import numpy as np import cv2 from scipy.signal import find_peaks# 示例数据 y_data [365.63258786, 318.34824281, 258.28434505, 228.8913738, 190.87220447, 158.28434505, 129.53035144, 111.95846645, 111.95846645, 120.26517572, 140.71246006, 161.79872204, 180.…...
Windows 蓝牙驱动开发-蓝牙设备栈
蓝牙设备栈 蓝牙驱动程序堆栈包含 Microsoft 为蓝牙协议提供支持的核心部分。 有了这个堆栈,已启用蓝牙的设备可以彼此定位并建立连接。 在此类连接中,设备可以通过各种应用程序交换数据并彼此交互。 下图显示了蓝牙驱动程序堆栈中的模块,以…...
css hover样式调试
调试 hover后才出现的元素如何调试 打开开发者工具,鼠标放在hover时才出现的元素上,然后点击右键不要选中任何选项,将鼠标移动到开发者工具的调试面板中按下N键,此时悬浮的元素不会消失,定位成功 调试元素悬浮样式 …...
【Unity】unity3D 调用LoadSceneAsync 场景切换后比较暗 部门材质丢失
解决方法:两个场景使用同样灯光 现象 直接进入第二个场景是可以正常显示 调用LoadSceneAsync来切换后,第二个场景出现比较暗的情况 解决方法:两个场景使用同样灯光,在loading 的场景中加入灯光。 Light—Directional Light 如果…...
代码随想录二刷|字符串总结
代码随想录二刷|字符串 反转数字 题干 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。 思路 class Soluti…...