├── .ci-scripts ├── config_release.sh ├── init_func_test.sh ├── init_perf_test.sh ├── init_system_test.sh ├── ip_mapping_1_to_2.sh ├── perf_add_source.tcl ├── pll.tcl ├── search.sh ├── synth_impl.tcl └── verilog_preprocess.sh ├── .gitignore ├── .gitlab-ci.yml ├── .scalafmt.conf ├── LICENSE ├── README.md ├── build.sbt ├── build.sc ├── docs ├── NOP_presentation.pdf └── NOP_report.pdf ├── mill-loongarch32r-linux-gnusf.Dockerfile ├── src ├── Main.scala ├── MainAdapted.scala ├── MyCPU.scala ├── MyCPUAdapted.scala ├── MyCPUConfig.scala ├── blackbox │ ├── execute │ │ └── Multiplier.scala │ └── mem │ │ ├── SDPRAM.scala │ │ ├── xpm_fifo.scala │ │ └── xpm_memory.scala ├── builder │ ├── MultiPipeline.scala │ ├── Pipeline.scala │ ├── Plugin.scala │ └── Stage.scala ├── constants │ ├── LoongArch.scala │ └── enum │ │ ├── ALUOpType.scala │ │ ├── CacheOpType.scala │ │ ├── CompareOpType.scala │ │ ├── ExtendType.scala │ │ ├── FUType.scala │ │ ├── LoadStoreType.scala │ │ ├── MemOperationType.scala │ │ ├── PrivModeType.scala │ │ └── TLBOpType.scala ├── debug │ └── DebugInterface.scala ├── peripheral │ ├── AxiBuffer.scala │ └── AxiCrossbar.scala ├── pipeline │ ├── core │ │ ├── BypassNetworkPlugin.scala │ │ ├── CommitPlugin.scala │ │ ├── CommitTraits.scala │ │ ├── ExceptionMuxPlugin.scala │ │ ├── MyCPUCore.scala │ │ ├── PhysRegFilePlugin.scala │ │ ├── ROBFIFOPlugin.scala │ │ ├── ReorderBufferModel.scala │ │ ├── SpeculativeWakeupHandler.scala │ │ └── Timer64Plugin.scala │ ├── decode │ │ ├── DecodeMicroOP.scala │ │ ├── DecodePipeline.scala │ │ ├── DecodeSignals.scala │ │ ├── DecoderArrayPlugin.scala │ │ ├── InstructionParser.scala │ │ ├── RenameModel.scala │ │ └── RenamePlugin.scala │ ├── exe │ │ ├── ALU.scala │ │ ├── BRU.scala │ │ ├── Comparator.scala │ │ ├── ExecutePipeline.scala │ │ ├── IntExecutePlugin.scala │ │ ├── IntIssueQueuePlugin.scala │ │ ├── IssueSlot.scala │ │ ├── MulDivExecutePlugin.scala │ │ └── MulDivIssueQueuePlugin.scala │ ├── fetch │ │ ├── BranchPredictModel.scala │ │ ├── FetchBufferPlugin.scala │ │ ├── FetchDebug.scala │ │ ├── FetchPipeline.scala │ │ ├── FetchSignals.scala │ │ ├── GlobalPredictorBTBPlugin.scala │ │ ├── ICachePlugin.scala │ │ ├── InstAddrTranslationPlugin.scala │ │ ├── ProgramCounterPlugin.scala │ │ └── ReturnAddressStackPlugin.scala │ ├── mem │ │ ├── AddressGenerationPlugin.scala │ │ ├── DCachePlugin.scala │ │ ├── DirtyBitsManager.scala │ │ ├── LoadPostProcessPlugin.scala │ │ ├── MemExecutePlugin.scala │ │ ├── MemIssueQueuePlugin.scala │ │ ├── MemPipeline.scala │ │ ├── StoreBufferPlugin.scala │ │ └── UncachedAccess.scala │ └── privilege │ │ ├── CSRPlugin.scala │ │ ├── ExceptionHandlerPlugin.scala │ │ ├── InterruptHandlerPlugin.scala │ │ ├── MMUPlugin.scala │ │ └── WaitHandlerPlugin.scala └── utils │ ├── Axi4Rename.scala │ ├── CompressedFIFO.scala │ ├── CompressedQueue.scala │ ├── Defines.scala │ ├── MultiPortFIFO.scala │ ├── MultiPortFIFOVec.scala │ └── ReorderCacheRAM.scala ├── system_test_scratch.Dockerfile └── xilinx_ip └── multiplier.xci /.ci-scripts/config_release.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | set -ex 4 | 5 | source ~/.bashrc 6 | 7 | # build test scripts 8 | make -C ${FUNC_ROOT}/func clean 9 | make -C ${FUNC_ROOT}/func 10 | make -C ${PERF_ROOT}/soft/perf_func clean 11 | make -C ${PERF_ROOT}/soft/perf_func 12 | 13 | # relax permissions 14 | chmod -R 777 ${TEAM_ROOT} 15 | 16 | # fix mis-comment in CDP_EDE_local @ introduced by c4f09a3d30917cd90a1672b6297022578512404c 17 | sed -i 's/# add_files -scan_for_includes ..\/..\/..\/myCPU/add_files -scan_for_includes ..\/..\/..\/myCPU/g' ${FUNC_ROOT}/soc_verify/soc_axi/run_vivado/create_project.tcl 18 | -------------------------------------------------------------------------------- /.ci-scripts/init_func_test.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | set -ex 4 | 5 | # executables 6 | # VIVADO=/opt/Xilinx/Vivado/2019.2/bin/vivado 7 | 8 | # sources 9 | # CPU_DIR=/home/scc23nop1/lambda/team-latest/cpu-src 10 | # IP_DIR=/home/scc23nop1/lambda/team-latest/cpu-src 11 | # SCRIPT_DIR=/home/scc23nop1/lambda/team-latest/.ci-scripts 12 | 13 | # destination 14 | # TEAM_ROOT=~/lambda/team-latest 15 | 16 | # assert environment variables 17 | if [ -z "${VIVADO}" ]; then 18 | echo "VIVADO is not set" 19 | exit 1 20 | fi 21 | 22 | if [ -z "${CPU_DIR}" ]; then 23 | echo "CPU_DIR is not set" 24 | exit 1 25 | fi 26 | 27 | if [ -z "${IP_DIR}" ]; then 28 | echo "IP_DIR is not set" 29 | exit 1 30 | fi 31 | 32 | if [ -z "${SCRIPT_DIR}" ]; then 33 | echo "SCRIPT_DIR is not set" 34 | exit 1 35 | fi 36 | 37 | if [ -z "${FUNC_ROOT}" ]; then 38 | echo "FUNC_ROOT is not set" 39 | exit 1 40 | fi 41 | 42 | if [ -z "${FUNC_RESULT}" ]; then 43 | echo "FUNC_RESULT is not set" 44 | exit 1 45 | fi 46 | 47 | IP_ROOT=${FUNC_ROOT}/soc_verify/soc_axi/rtl/xilinx_ip 48 | SRC_ROOT=${FUNC_ROOT}/myCPU 49 | RUN_VIVADO_ROOT=${FUNC_ROOT}/soc_verify/soc_axi/run_vivado 50 | 51 | mkdir ${SRC_ROOT} 52 | 53 | cp ${CPU_DIR}/*.v ${SRC_ROOT} 54 | for ip in `ls ${IP_DIR}/*.xci`; do 55 | ip_name=`basename $ip` 56 | ip_name=${ip_name%.*} 57 | ip_dir=${IP_ROOT}/${ip_name} 58 | mkdir ${ip_dir} 59 | cp ${ip} ${ip_dir} 60 | done 61 | 62 | pushd ${RUN_VIVADO_ROOT} 63 | 64 | ${VIVADO} -mode batch -source create_project.tcl 65 | 66 | popd 67 | 68 | # copy project 69 | cp -r ${FUNC_ROOT} ${FUNC_RESULT} 70 | 71 | # generate bitstream 72 | ${VIVADO} -mode batch -source ${SCRIPT_DIR}/synth_impl.tcl ${RUN_VIVADO_ROOT}/project/loongson.xpr 73 | 74 | # copy bitstream 75 | cp ${RUN_VIVADO_ROOT}/project/loongson.runs/impl_1/*.bit ${FUNC_RESULT} 76 | -------------------------------------------------------------------------------- /.ci-scripts/init_perf_test.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | set -ex 4 | 5 | # build args 6 | # export PLL_FREQ=100 7 | # export JOBS_NUMBER=8 8 | 9 | # executables 10 | # VIVADO=/opt/Xilinx/Vivado/2019.2/bin/vivado 11 | 12 | # sources 13 | # CPU_DIR=/home/scc23nop1/lambda/team-latest/cpu-src 14 | # IP_DIR=/home/scc23nop1/lambda/team-latest/cpu-src 15 | # SCRIPT_DIR=/home/scc23nop1/lambda/team-latest/.ci-scripts 16 | 17 | # destination 18 | # TEAM_ROOT=~/lambda/team-latest 19 | 20 | # assert environment variables 21 | if [ -z "${VIVADO}" ]; then 22 | echo "VIVADO is not set" 23 | exit 1 24 | fi 25 | 26 | if [ -z "${CPU_DIR}" ]; then 27 | echo "CPU_DIR is not set" 28 | exit 1 29 | fi 30 | 31 | if [ -z "${IP_DIR}" ]; then 32 | echo "IP_DIR is not set" 33 | exit 1 34 | fi 35 | 36 | if [ -z "${SCRIPT_DIR}" ]; then 37 | echo "SCRIPT_DIR is not set" 38 | exit 1 39 | fi 40 | 41 | if [ -z "${PERF_ROOT}" ]; then 42 | echo "PERF_ROOT is not set" 43 | exit 1 44 | fi 45 | 46 | IP_ROOT=${PERF_ROOT}/soc_axi_perf/rtl/xilinx_ip_extra 47 | SRC_ROOT=${PERF_ROOT}/soc_axi_perf/rtl/myCPU 48 | PROJ_ROOT=${PERF_ROOT}/soc_axi_perf/run_vivado/mycpu_prj1 49 | 50 | mkdir ${SRC_ROOT} 51 | mkdir ${IP_ROOT} 52 | 53 | cp ${CPU_DIR}/*.v ${SRC_ROOT} 54 | for ip in `ls ${IP_DIR}/*.xci`; do 55 | ip_name=`basename $ip` 56 | ip_name=${ip_name%.*} 57 | ip_dir=${IP_ROOT}/${ip_name} 58 | mkdir ${ip_dir} 59 | cp ${ip} ${ip_dir} 60 | done 61 | 62 | # add source to project 63 | ${VIVADO} -mode batch -source ${SCRIPT_DIR}/perf_add_source.tcl ${PROJ_ROOT}/mycpu.xpr 64 | 65 | # config pll frequency 66 | ${VIVADO} -mode batch -source ${SCRIPT_DIR}/pll.tcl ${PROJ_ROOT}/mycpu.xpr 67 | 68 | # copy project 69 | cp -r ${PERF_ROOT} ${PERF_RESULT} 70 | 71 | # generate bitstream 72 | ${VIVADO} -mode batch -source ${SCRIPT_DIR}/synth_impl.tcl ${PROJ_ROOT}/mycpu.xpr 73 | 74 | # copy bitstream 75 | cp ${PROJ_ROOT}/mycpu.runs/impl_1/*.bit ${PERF_RESULT} 76 | 77 | # copy clock 78 | cp ${PERF_ROOT}/soc_axi_perf/rtl/xilinx_ip/clk_pll/clk_pll.xci ${PERF_RESULT}/perf_clk_pll.xci 79 | -------------------------------------------------------------------------------- /.ci-scripts/init_system_test.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | set -ex 4 | 5 | mkdir -p ${SYSTEM_RESULT} 6 | 7 | cp ./original_build/mycpu_top.v /linux_run/nop_cpu/mycpu_top.v 8 | 9 | # generate bitstream 10 | SKIP_TIMING_CHECK=1 ${VIVADO} -mode batch -source ${SCRIPT_DIR}/synth_impl.tcl /linux_run/fpga/loongson_mycpu/system_run/system_run.xpr 11 | 12 | # copy bitstream 13 | cp /linux_run/fpga/loongson_mycpu/system_run/system_run.runs/impl_1/*.bit ${SYSTEM_RESULT} -------------------------------------------------------------------------------- /.ci-scripts/ip_mapping_1_to_2.sh: -------------------------------------------------------------------------------- 1 | sed -i 's/"PROJECT_PARAM.SPEEDGRADE">-1-2 本项目是第七届“龙芯杯”全国大学生计算机系统能力培养大赛 (NSCSCC 2023) 的参赛作品。项目成功地开发了一款基于龙芯架构 32 位精简版 (LoongArch32-Reduced, LA32R) 指令集的 CPU,命名为 NOP。NOP 属于乱序多发射微架构,整体基于 Tomasulo 动态调度算法的思路 + 重排序缓存实现,并实现了分支预测、指令/数据缓存、数据旁路、推测唤醒等特性。从功能的角度上,NOP 实现了基础的算术指令、分支指令、访存指令,支持精确异常处理、虚实地址转换与 LA32R 指令集规定的各类中断,通过使用 PMON 引导程序,NOP 可以稳定地启动Linux 操作系统。从性能上看,NOP 作为一款乱序多发射处理器,在大赛提供的 FPGA 实验平台与性能测试程序 / SoC 上达到了 107.69 MHz 的主频与 1.02 的 IPC。相比于基线 openLA500 处理器,NOP 核的整体加速比达到了 3.00,IPC 加速比达到了 1.402. 9 | 10 | ## Usage 11 | 12 | Java development kit (JDK) is required to run the project. We use `sbt` as our build tool. To build the project, you need to install `sbt` first. Then, you can run the following command to build the project: 13 | 14 | ```bash 15 | sbt run 16 | ``` 17 | 18 | Then you can see the build result in `build` directory. Simply copy `mycpu_top.v` to your chiplab FPGA project and you can simulate / run it on FPGA. 19 | 20 | 21 | ## Project Structure 22 | 23 | There are several subfolders in the project: 24 | 25 | + `.ci-scripts`: Scripts used in CI. 26 | + `docs`: Documentation of the project. 27 | + `src`: Source code of the project. 28 | + `xilinx_ip`: Xilinx IP cores used in the project. 29 | 30 | In `src` folder, there are several subfolders: 31 | 32 | + `blackbox`: Blackbox modules used in the project. (e.g. Multiplier, xpm_memory) 33 | + `builder`: Taken from VexRiscv project. Contains the definition of the class of Stage, Plugin and Pipeline. 34 | + `constants`: Contains the definition of constants used in the project, mainly for the LA32R ISA. 35 | + `debug`: Contains the definition of debug module. (Not used. We use difftest signals instead.) 36 | + `peripheral`: Contains the definition of peripheral modules. (e.g. AxiBuffer, AxiCrossbar) 37 | + `pipeline`: Contains the definition of pipeline modules. 38 | + `utils`: Contains the definition of utility modules. 39 | 40 | The `src/pipeline` folder is the heart of our NOP processor's pipeline structure. We dissect the pipeline of NOP CPU into various stages and components, implementing each as Scala modules. 41 | 42 | - `core`: This folder houses the central architectural files for the NOP-Core pipeline. Modules here handle key tasks like bypassing network operations (`BypassNetworkPlugin.scala`), managing commit operations and traits (`CommitPlugin.scala`, `CommitTraits.scala`), managing exceptions (`ExceptionMuxPlugin.scala`), and operating on the Reorder Buffer (`ROBFIFOPlugin.scala`, `ReorderBufferModel.scala`), among others. 43 | 44 | - `decode`: The decode phase of a CPU pipeline is where instructions fetched from memory are translated into actions the CPU can understand. This directory appears to deal with decoding micro-operations (`DecodeMicroOP.scala`), the actual decoding pipeline (`DecodePipeline.scala`), signals for decoding (`DecodeSignals.scala`), and renaming of registers (`RenameModel.scala`, `RenamePlugin.scala`). 45 | 46 | - `exe`: Short for "execute," this directory concerns itself with the CPU's execution stage. Files here manage ALU (Arithmetic Logic Unit) operations (`ALU.scala`), branch unit operations (`BRU.scala`), comparisons (`Comparator.scala`), integer executions (`IntExecutePlugin.scala`), and multiplication and division tasks (`MulDivExecutePlugin.scala`). 47 | 48 | - `fetch`: The fetch phase is the first step in a CPU's operation cycle where instructions are retrieved from memory. Modules in this directory cater to functionalities like branch prediction (`BranchPredictModel.scala`), buffering of fetched data (`FetchBufferPlugin.scala`), and instruction caching (`ICachePlugin.scala`). 49 | 50 | - `mem`: As the name suggests, this folder focuses on memory operations within the CPU pipeline. This includes address generation (`AddressGenerationPlugin.scala`), managing data cache (`DCachePlugin.scala`), and ensuring the correct retrieval of data and the in-order commit for store / uncached load instructions with modules like `LoadPostProcessPlugin.scala` and `StoreBufferPlugin.scala`. 51 | 52 | - `privilege`: This directory seems to center around privileged operations and states of the CPU. It features modules for Control Status Registers (`CSRPlugin.scala`), exception handling (`ExceptionHandlerPlugin.scala`), interrupt handling (`InterruptHandlerPlugin.scala`), and the Memory Management Unit (`MMUPlugin.scala`). 53 | 54 | 55 | ## Supplementary Resources 56 | 57 | Also see our: 58 | 59 | - SoC Design: [NOP-SoC](https://github.com/NOP-Processor/NOP-SoC) 60 | - PMON Adaptation: [NOP-PMON](https://github.com/NOP-Processor/NOP-PMON) 61 | - Linux Adaptation: [NOP-Linux](https://github.com/NOP-Processor/NOP-Linux) 62 | - Submission and Presentation Objects: [NOP-Misc](https://github.com/NOP-Processor/NOP-Misc) 63 | 64 | ## Acknowledgement 65 | 66 | We are grateful to the following open-source projects: 67 | 68 | + [ZenCove](https://github.com/zencove-thu/zencove-zoom) 69 | + [VexRiscv](https://github.com/SpinalHDL/VexRiscv) 70 | + [openLA500](https://gitee.com/loongson-edu/nscscc-openla500) 71 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "1.0" 2 | ThisBuild / scalaVersion := "2.12.16" 3 | ThisBuild / organization := "NOP" 4 | 5 | val spinalVersion = "1.8.1" 6 | 7 | lazy val projectname = project.in(file(".")) 8 | .settings( 9 | Compile / scalaSource := baseDirectory.value / "src", 10 | libraryDependencies ++= Seq( 11 | "com.github.spinalhdl" %% "spinalhdl-core" % spinalVersion, 12 | "com.github.spinalhdl" %% "spinalhdl-lib" % spinalVersion, 13 | "com.github.scopt" %% "scopt" % "4.0.1", 14 | compilerPlugin("com.github.spinalhdl" %% "spinalhdl-idsl-plugin" % spinalVersion), 15 | compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full) 16 | ) 17 | ) 18 | 19 | // fork := true 20 | -------------------------------------------------------------------------------- /build.sc: -------------------------------------------------------------------------------- 1 | // build.sc 2 | import mill._, scalalib._ 3 | import $ivy.`com.lihaoyi::mill-contrib-bloop:$MILL_VERSION` 4 | 5 | val spinalVersion = "1.8.0" 6 | 7 | object NOP extends SbtModule { 8 | def scalaVersion = "2.12.16" 9 | override def mainClass = Some("NOP.Main") 10 | 11 | override def millSourcePath = os.pwd 12 | def sources = T.sources( 13 | millSourcePath / "src" 14 | ) 15 | 16 | def ivyDeps = Agg( 17 | ivy"com.github.spinalhdl::spinalhdl-core:$spinalVersion", 18 | ivy"com.github.spinalhdl::spinalhdl-lib:$spinalVersion", 19 | ivy"com.github.scopt::scopt:4.0.1" 20 | ) 21 | 22 | def scalacPluginIvyDeps = Agg( 23 | ivy"com.github.spinalhdl::spinalhdl-idsl-plugin:$spinalVersion", 24 | ivy"org.scalamacros:::paradise:2.1.1" 25 | ) 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/NOP_presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NOP-Processor/NOP-Core/1a5986d9d1ff02d2156fb4d065e5ad0ba0f94495/docs/NOP_presentation.pdf -------------------------------------------------------------------------------- /docs/NOP_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NOP-Processor/NOP-Core/1a5986d9d1ff02d2156fb4d065e5ad0ba0f94495/docs/NOP_report.pdf -------------------------------------------------------------------------------- /mill-loongarch32r-linux-gnusf.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM cuibst/scala-mill:latest 2 | 3 | # this is a Debain 11 image 4 | 5 | SHELL ["/bin/bash", "-c"] 6 | 7 | # config toolchains 8 | RUN wget https://gitee.com/loongson-edu/la32r-toolchains/releases/download/v0.0.2/loongarch32r-linux-gnusf-2022-05-20-x86.tar.gz 9 | RUN tar -xvf loongarch32r-linux-gnusf-2022-05-20-x86.tar.gz 10 | RUN rm loongarch32r-linux-gnusf-2022-05-20-x86.tar.gz 11 | RUN echo 'export PATH=/root/loongarch32r-linux-gnusf-2022-05-20/bin:$PATH' >> .bashrc 12 | 13 | # install `make` 14 | RUN apt-get update 15 | RUN apt-get update && apt-get install -y make gcc g++ 16 | RUN rm -rf /var/lib/apt/lists/* 17 | 18 | # as mill-loongarch32r-linux-gnusf -------------------------------------------------------------------------------- /src/Main.scala: -------------------------------------------------------------------------------- 1 | package NOP 2 | 3 | import spinal.core._ 4 | import spinal.lib.bus.amba4.axi._ 5 | import spinal.lib._ 6 | 7 | import NOP._ 8 | 9 | object Main { 10 | def main(args: Array[String]): Unit = { 11 | SpinalConfig( 12 | targetDirectory = "./build", 13 | headerWithDate = true 14 | ).generateVerilog(new MyCPU(new MyCPUConfig())) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/MainAdapted.scala: -------------------------------------------------------------------------------- 1 | package NOP 2 | 3 | import spinal.core._ 4 | import spinal.lib.bus.amba4.axi._ 5 | import spinal.lib._ 6 | 7 | import NOP._ 8 | 9 | object MainAdapted { 10 | def main(args: Array[String]): Unit = { 11 | SpinalConfig( 12 | targetDirectory = "./build", 13 | headerWithDate = true 14 | ).generateVerilog(new MyCPUAdapted(new MyCPUConfig())) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/MyCPUAdapted.scala: -------------------------------------------------------------------------------- 1 | package NOP 2 | 3 | import NOP.constants.LoongArch.CSRAddress 4 | import NOP.constants.`enum`.{CacheOpType, CacheSelType, LoadStoreType, TLBOpType} 5 | import spinal.core.{Bits, _} 6 | import spinal.lib.bus.amba4.axi._ 7 | import spinal.lib._ 8 | import NOP.debug._ 9 | import NOP.pipeline.core._ 10 | import NOP.pipeline.priviledge._ 11 | import NOP.utils._ 12 | 13 | class MyCPUAdapted(config: MyCPUConfig) extends Component { 14 | setDefinitionName("mycpu_top") 15 | noIoPrefix() 16 | 17 | val io = new Bundle { 18 | val aclk = in(Bool) 19 | val aresetn = in(Bool) 20 | // Interrupts 21 | val intrpt = in(Bits(8 bits)) 22 | 23 | val iBus_ = master(Axi4(config.axiConfig)) 24 | val dBus_ = master(Axi4(config.axiConfig)) 25 | val udBus_ = master(Axi4(config.axiConfig)) 26 | } 27 | 28 | val defaultClockDomain = ClockDomain( 29 | clock = io.aclk, 30 | reset = io.aresetn, 31 | config = ClockDomainConfig(resetActiveLevel = LOW) 32 | ) 33 | 34 | val defaultClockArea = new ClockingArea(defaultClockDomain) { 35 | val cpu = new NOP.pipeline.core.MyCPUCore(config) 36 | 37 | // Connect io to CPUCore 38 | cpu.io.intrpt := io.intrpt 39 | 40 | cpu.io.iBus <> io.iBus_ 41 | cpu.io.dBus <> io.dBus_ 42 | cpu.io.udBus <> io.udBus_ 43 | 44 | }.setCompositeName(this) 45 | 46 | addPrePopTask { () => 47 | Axi4Rename.Rename(io) // To make Axi4 signals match with loongchip 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/MyCPUConfig.scala: -------------------------------------------------------------------------------- 1 | package NOP 2 | 3 | import spinal.core._ 4 | import spinal.lib.bus.amba4.axi._ 5 | import spinal.lib._ 6 | 7 | abstract class CacheBasicConfig { 8 | val sets: Int 9 | val lineSize: Int 10 | val ways: Int 11 | val offsetWidth = log2Up(lineSize) 12 | val offsetRange = (offsetWidth - 1) downto 0 13 | val wordOffsetRange = (offsetWidth - 1) downto 2 14 | val indexWidth = log2Up(sets) 15 | val indexRange = (offsetWidth + indexWidth - 1) downto offsetWidth 16 | val tagOffset = offsetWidth + indexWidth 17 | val tagRange = 31 downto tagOffset 18 | def wordCount = sets * lineSize / 4 19 | def lineWords = lineSize / 4 20 | } 21 | 22 | final case class ICacheConfig( 23 | sets: Int = 64, 24 | lineSize: Int = 64, 25 | ways: Int = 2, 26 | useReorder: Boolean = false 27 | ) extends CacheBasicConfig { 28 | require(sets * lineSize <= (4 << 10), "4KB is VIPT limit") 29 | val enable = true 30 | } 31 | 32 | final case class DCacheConfig( 33 | sets: Int = 64, 34 | lineSize: Int = 64, 35 | ways: Int = 2 36 | ) extends CacheBasicConfig { 37 | require(sets * lineSize <= (4 << 10), "4KB is VIPT limit") 38 | val enable = true 39 | } 40 | 41 | final case class BTBConfig( 42 | sets: Int = 1024, 43 | lineSize: Int = 4, 44 | ways: Int = 1, 45 | rasEntries: Int = 8 46 | ) extends CacheBasicConfig { 47 | val enable = true 48 | } 49 | 50 | final case class BPUConfig( 51 | sets: Int = 1024, 52 | phtSets: Int = 8192, 53 | historyWidth: Int = 5, 54 | counterWidth: Int = 2 55 | ) { 56 | val indexWidth = log2Up(sets) 57 | val indexRange = 2 until 2 + indexWidth 58 | val ways = 1 << historyWidth 59 | val phtIndexWidth = log2Up(phtSets) 60 | val phtPCRange = 2 until 2 + phtIndexWidth - historyWidth 61 | val counterType = HardType(UInt(counterWidth bits)) 62 | val historyType = HardType(UInt(historyWidth bits)) 63 | val useGlobal = true 64 | val useLocal = false 65 | val useHybrid = false 66 | } 67 | 68 | final case class FrontendConfig( 69 | pcInit: Long = 0x1c000000L, 70 | icache: ICacheConfig = ICacheConfig(), 71 | btb: BTBConfig = BTBConfig(), 72 | bpu: BPUConfig = BPUConfig(), 73 | fetchWidth: Int = 4, 74 | fetchBufferDepth: Int = 8 75 | ) 76 | 77 | final case class RegFileConfig( 78 | nArchRegs: Int = 32, 79 | nPhysRegs: Int = 31 + 32 80 | ) { 81 | val arfAddrWidth = log2Up(nArchRegs) 82 | val prfAddrWidth = log2Up(nPhysRegs) 83 | val rPortsEachInst = 2 84 | } 85 | 86 | final case class DecodeConfig( 87 | decodeWidth: Int = 3, 88 | allUnique: Boolean = false 89 | ) 90 | 91 | // Issuing 92 | abstract class IssueConfig { 93 | val issueWidth: Int 94 | val depth: Int 95 | val addrWidth = log2Up(depth) 96 | } 97 | 98 | final case class IntIssueConfig( 99 | issueWidth: Int = 3, 100 | bruIdx: Int = 0, 101 | csrIdx: Int = 0, 102 | timerIdx: Int = 1, 103 | invTLBIdx: Int = 0, 104 | depth: Int = 7 105 | ) extends IssueConfig { 106 | require(0 <= bruIdx && bruIdx < issueWidth) 107 | require(0 <= csrIdx && csrIdx < issueWidth) 108 | require(0 <= timerIdx && timerIdx < issueWidth) 109 | require(0 <= invTLBIdx && invTLBIdx < issueWidth) 110 | } 111 | 112 | final case class MulDivConfig( 113 | depth: Int = 3, 114 | multiplyLatency: Int = 2, 115 | divisionEarlyOutWidth: Int = 16 // set to 0 to disable early out 116 | ) extends IssueConfig { 117 | val issueWidth: Int = 1 118 | def useDivisionEarlyOut = divisionEarlyOutWidth > 0 119 | } 120 | 121 | final case class MemIssueConfig( 122 | depth: Int = 5 123 | ) extends IssueConfig { 124 | val issueWidth: Int = 1 125 | } 126 | 127 | // Commit 128 | final case class ROBConfig( 129 | robDepth: Int = 32, 130 | retireWidth: Int = 3 131 | ) { 132 | val robAddressWidth = log2Up(robDepth) 133 | } 134 | 135 | // Interrupt 136 | final case class InterruptConfig( 137 | innerCounterDownEvery: Int = 1 138 | ) 139 | 140 | // TLB 141 | final case class TLBConfig( 142 | numEntries: Int = 16, 143 | physAddrWidth: Int = 32 144 | ) { 145 | val indexWidth = log2Up(numEntries) 146 | val virtAddrWidth = 32 147 | val asidWidth = 10 148 | } 149 | 150 | final case class MyCPUConfig( 151 | axiConfig: Axi4Config = Axi4Config( 152 | addressWidth = 32, 153 | dataWidth = 32, 154 | idWidth = 4, 155 | useRegion = false, 156 | useQos = false 157 | ), 158 | debug: Boolean = true, 159 | weDebug: Boolean = true, 160 | debug_difftest: Boolean = true, 161 | frontend: FrontendConfig = FrontendConfig(), 162 | decode: DecodeConfig = DecodeConfig(), 163 | regFile: RegFileConfig = RegFileConfig(), 164 | // EXE 165 | intIssue: IntIssueConfig = IntIssueConfig(), 166 | mulDiv: MulDivConfig = MulDivConfig(), 167 | // MEM 168 | dcache: DCacheConfig = DCacheConfig(), 169 | memIssue: MemIssueConfig = MemIssueConfig(), 170 | storeBufferDepth: Int = 8, 171 | // Commit 172 | rob: ROBConfig = ROBConfig(), 173 | // Interrupt 174 | interrupt: InterruptConfig = InterruptConfig(), 175 | // TLB 176 | tlbConfig: TLBConfig = TLBConfig() 177 | ) 178 | -------------------------------------------------------------------------------- /src/blackbox/execute/Multiplier.scala: -------------------------------------------------------------------------------- 1 | package NOP.blackbox.execute 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import spinal.sim._ 6 | 7 | class Multiplier(dataWidth: Int = 32, name: String = "multiplier") extends BlackBox { 8 | setDefinitionName(name) 9 | noIoPrefix() 10 | val io = new Bundle { 11 | val CLK = in Bool () 12 | val A = in UInt (dataWidth bits) 13 | val B = in UInt (dataWidth bits) 14 | val P = out UInt (dataWidth * 2 bits) 15 | } 16 | mapClockDomain(clock = io.CLK) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/blackbox/mem/SDPRAM.scala: -------------------------------------------------------------------------------- 1 | package NOP.blackbox.mem 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | object noChange extends ReadUnderWritePolicy { 7 | override def readUnderWriteString: String = "noChange" 8 | } 9 | 10 | final case class SDPRAMSyncIOBundle[T <: Data]( 11 | wordType: HardType[T], 12 | wordCount: Int, 13 | maskWidth: Int = -1 14 | ) extends Bundle { 15 | val read = slave(MemReadPort(wordType(), log2Up(wordCount))) 16 | val write = in(Flow(MemWriteCmd(Mem(wordType, wordCount)))) 17 | val writeMask = maskWidth > 0 generate in(Bits(maskWidth bits)) 18 | } 19 | 20 | trait SDPRAMSyncIO[T <: Data] { 21 | val io: SDPRAMSyncIOBundle[T] 22 | val memGeneric: xpm_memory_sdpram_generic 23 | } 24 | 25 | class SDPRAM[T <: Data]( 26 | wordType: HardType[T], 27 | wordCount: Int, 28 | outputReg: Boolean, 29 | policy: ReadUnderWritePolicy = readFirst, 30 | simMessage: Boolean = true, 31 | useByteEnable: Boolean = false 32 | ) extends Component 33 | with SDPRAMSyncIO[T] { 34 | val addressWidth = log2Up(wordCount) 35 | val wordWidth = wordType.getBitsWidth 36 | val maskWidth = if (useByteEnable) wordWidth / 8 else -1 37 | if (useByteEnable) require(wordWidth % 8 == 0) 38 | val memGeneric = new xpm_memory_sdpram_generic 39 | memGeneric.ADDR_WIDTH_A = addressWidth 40 | memGeneric.ADDR_WIDTH_B = addressWidth 41 | memGeneric.BYTE_WRITE_WIDTH_A = if (useByteEnable) 8 else wordWidth 42 | memGeneric.MEMORY_SIZE = wordWidth * wordCount 43 | memGeneric.READ_DATA_WIDTH_B = wordWidth 44 | memGeneric.READ_LATENCY_B = if (outputReg) 2 else 1 45 | memGeneric.SIM_ASSERT_CHK = simMessage.toInt 46 | memGeneric.WRITE_DATA_WIDTH_A = wordWidth 47 | memGeneric.WRITE_MODE_B = policy.readUnderWriteString.replaceAll("([A-Z])", "_$1").toLowerCase() 48 | val mem = new xpm_memory_sdpram(memGeneric) 49 | val io = SDPRAMSyncIOBundle(wordType, wordCount, maskWidth) 50 | mem.io.read.en := io.read.cmd.valid 51 | mem.io.read.regce := io.read.cmd.valid 52 | mem.io.read.addr := io.read.cmd.payload 53 | io.read.rsp.assignFromBits(mem.io.read.dout) 54 | 55 | mem.io.write.en := io.write.valid 56 | mem.io.write.we := (if (useByteEnable) io.writeMask else mem.io.write.we.getAllTrue) 57 | mem.io.write.addr := io.write.payload.address 58 | mem.io.write.din := io.write.payload.data.asBits 59 | } 60 | 61 | class SDPRAMAsync[T <: Data]( 62 | wordType: HardType[T], 63 | wordCount: Int, 64 | policy: ReadUnderWritePolicy = readFirst, 65 | simMessage: Boolean = true 66 | ) extends Component { 67 | val addressWidth = log2Up(wordCount) 68 | val wordWidth = wordType.getBitsWidth 69 | 70 | val memGeneric = new xpm_memory_sdpram_generic 71 | memGeneric.ADDR_WIDTH_A = addressWidth 72 | memGeneric.ADDR_WIDTH_B = addressWidth 73 | memGeneric.BYTE_WRITE_WIDTH_A = wordWidth // WE为1位,暂不使用 74 | memGeneric.MEMORY_SIZE = wordWidth * wordCount 75 | memGeneric.READ_DATA_WIDTH_B = wordWidth 76 | memGeneric.READ_LATENCY_B = 0 77 | memGeneric.SIM_ASSERT_CHK = simMessage.toInt 78 | memGeneric.WRITE_DATA_WIDTH_A = wordWidth 79 | memGeneric.WRITE_MODE_B = policy.readUnderWriteString.replaceAll("([A-Z])", "_$1").toLowerCase() 80 | val mem = new xpm_memory_sdpram(memGeneric) 81 | val io = new Bundle { 82 | val read = slave(MemReadPortAsync(wordType(), addressWidth)) 83 | val write = in(Flow(MemWriteCmd(Mem(wordType, wordCount)))) 84 | } 85 | mem.io.read.en := True 86 | mem.io.read.regce := True 87 | mem.io.read.addr := io.read.address 88 | io.read.data.assignFromBits(mem.io.read.dout) 89 | 90 | mem.io.write.en := io.write.valid 91 | mem.io.write.we := mem.io.write.we.getAllTrue // 恒1 92 | mem.io.write.addr := io.write.payload.address 93 | mem.io.write.din := io.write.payload.data.asBits 94 | } 95 | 96 | class SDPRAMWriteFirst[T <: Data]( 97 | wordType: HardType[T], 98 | wordCount: Int 99 | ) extends SDPRAM(wordType, wordCount, false) { 100 | val regWriteData = RegNext(io.write.data) 101 | val regConflict = RegNext(io.write.valid && io.write.address === io.read.cmd.payload) 102 | when(regConflict)(io.read.rsp := regWriteData) 103 | } 104 | 105 | class SDPRAMWriteFirst2[T <: Data]( 106 | wordType: HardType[T], 107 | wordCount: Int 108 | ) extends SDPRAM(wordType, wordCount, false, policy = writeFirst) 109 | 110 | class SDPRAMAsyncToSyncWriteFirst[T <: Data]( 111 | wordType: HardType[T], 112 | wordCount: Int, 113 | outputReg: Boolean = true, 114 | policy: ReadUnderWritePolicy = readFirst, 115 | simMessage: Boolean = true 116 | ) extends Component 117 | with SDPRAMSyncIO[T] { 118 | val addressWidth = log2Up(wordCount) 119 | val wordWidth = wordType.getBitsWidth 120 | val memGeneric = new xpm_memory_sdpram_generic 121 | memGeneric.ADDR_WIDTH_A = addressWidth 122 | memGeneric.ADDR_WIDTH_B = addressWidth 123 | memGeneric.BYTE_WRITE_WIDTH_A = wordWidth // WE为1位,暂不使用 124 | memGeneric.MEMORY_SIZE = wordWidth * wordCount 125 | memGeneric.READ_DATA_WIDTH_B = wordWidth 126 | memGeneric.READ_LATENCY_B = 0 127 | memGeneric.SIM_ASSERT_CHK = simMessage.toInt 128 | memGeneric.WRITE_DATA_WIDTH_A = wordWidth 129 | memGeneric.WRITE_MODE_B = policy.readUnderWriteString.replaceAll("([A-Z])", "_$1").toLowerCase() 130 | val mem = new xpm_memory_sdpram(memGeneric) 131 | val io = SDPRAMSyncIOBundle(wordType, wordCount) 132 | mem.io.read.en := True 133 | mem.io.read.regce := True 134 | mem.io.read.addr := io.read.cmd.payload 135 | 136 | mem.io.write.en := io.write.valid 137 | mem.io.write.we := mem.io.write.we.getAllTrue // 恒1 138 | mem.io.write.addr := io.write.payload.address 139 | mem.io.write.din := io.write.payload.data.asBits 140 | 141 | // write first 142 | if (outputReg) { 143 | io.read.rsp.setAsReg() 144 | when(io.read.cmd.valid) { 145 | when(io.write.valid && io.write.payload.address === io.read.cmd.payload) { 146 | io.read.rsp := io.write.payload.data 147 | } otherwise { 148 | io.read.rsp.assignFromBits(mem.io.read.dout) 149 | } 150 | } 151 | } else { 152 | when(io.write.valid && io.write.payload.address === io.read.cmd.payload) { 153 | io.read.rsp := io.write.payload.data 154 | } otherwise { 155 | io.read.rsp.assignFromBits(mem.io.read.dout) 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/blackbox/mem/xpm_fifo.scala: -------------------------------------------------------------------------------- 1 | package NOP.blackbox.mem 2 | 3 | import spinal.core._ 4 | 5 | class xpm_fifo_sync_generic extends Generic { 6 | var DOUT_RESET_VALUE = "0" // String 7 | var ECC_MODE = "no_ecc" // String 8 | var FIFO_MEMORY_TYPE = "auto" // String 9 | var FIFO_READ_LATENCY = 1 // DECIMAL 10 | var FIFO_WRITE_DEPTH = 2048 // DECIMAL 11 | var FULL_RESET_VALUE = 0 // DECIMAL 12 | var PROG_EMPTY_THRESH = 10 // DECIMAL 13 | var PROG_FULL_THRESH = 10 // DECIMAL 14 | var RD_DATA_COUNT_WIDTH = 1 // DECIMAL 15 | var READ_DATA_WIDTH = 32 // DECIMAL 16 | var READ_MODE = "std" // String 17 | var SIM_ASSERT_CHK = 0 // DECIMAL; 0=disable simulation messages, 1=enable simulation messages 18 | var USE_ADV_FEATURES = "0707" // String 19 | var WAKEUP_TIME = 0 // DECIMAL 20 | var WRITE_DATA_WIDTH = 32 // DECIMAL 21 | var WR_DATA_COUNT_WIDTH = 1 // DECIMAL 22 | } 23 | 24 | class xpm_fifo_sync(param: xpm_fifo_sync_generic) extends BlackBox { 25 | val generic = param 26 | noIoPrefix() 27 | val io = new Bundle { 28 | val wr_clk, rst = in(Bool) 29 | val almost_empty, almost_full = out(Bool) 30 | val empty, full = out(Bool) 31 | val prog_empty, prog_full = out(Bool) 32 | val underflow, overflow = out(Bool) 33 | val data_valid = out(Bool) 34 | val dout = out(Bits(param.READ_DATA_WIDTH bits)) 35 | val injectsbiterr, injectdbiterr = in(Bool) default False 36 | val sleep = in(Bool) default False 37 | val sbiterr, dbiterr = out(Bool) 38 | val rd_rst_busy, wr_rst_busy = out(Bool) 39 | val rd_en, wr_en = in(Bool) 40 | val wr_ack = out(Bool) 41 | val din = in(Bits(param.WRITE_DATA_WIDTH bits)) 42 | val rd_data_count = out(UInt(param.RD_DATA_COUNT_WIDTH bits)) 43 | val wr_data_count = out(UInt(param.WR_DATA_COUNT_WIDTH bits)) 44 | } 45 | mapClockDomain(clock = io.wr_clk, reset = io.rst, resetActiveLevel = HIGH) 46 | } 47 | -------------------------------------------------------------------------------- /src/blackbox/mem/xpm_memory.scala: -------------------------------------------------------------------------------- 1 | package NOP.blackbox.mem 2 | 3 | import spinal.core._ 4 | 5 | class xpm_memory_sdpram_generic extends Generic { 6 | var ADDR_WIDTH_A = 6 7 | var ADDR_WIDTH_B = 6 8 | var AUTO_SLEEP_TIME = 0 9 | var BYTE_WRITE_WIDTH_A = 32 10 | var CASCADE_HEIGHT = 0 11 | var CLOCKING_MODE = "common_clock" 12 | var ECC_MODE = "no_ecc" 13 | var MEMORY_INIT_FILE = "none" 14 | var MEMORY_INIT_PARAM = "0" 15 | var MEMORY_OPTIMIZATION = "true" 16 | var MEMORY_PRIMITIVE = "auto" 17 | var MEMORY_SIZE = 2048 18 | var MESSAGE_CONTROL = 0 19 | var READ_DATA_WIDTH_B = 32 20 | var READ_LATENCY_B = 2 21 | var READ_RESET_VALUE_B = "0" 22 | var RST_MODE_A = "SYNC" 23 | var RST_MODE_B = "SYNC" 24 | var SIM_ASSERT_CHK = 0 25 | var USE_EMBEDDED_CONSTRAINT = 0 26 | var USE_MEM_INIT = 0 27 | var WAKEUP_TIME = "disable_sleep" 28 | var WRITE_DATA_WIDTH_A = 32 29 | var WRITE_MODE_B = "no_change" 30 | } 31 | 32 | class xpm_memory_sdpram(param: xpm_memory_sdpram_generic) extends BlackBox { 33 | val generic = param 34 | 35 | val io = new Bundle { 36 | val read = new Bundle { 37 | val clk = in(Bool) 38 | val rst = in(Bool) 39 | val en = in(Bool) 40 | val regce = in(Bool) 41 | val addr = in(UInt(generic.ADDR_WIDTH_B bits)) 42 | val dout = out(Bits(generic.READ_DATA_WIDTH_B bits)) 43 | val sbiterr = out(Bool) 44 | val dbiterr = out(Bool) 45 | } 46 | val write = new Bundle { 47 | val clk = in(Bool) 48 | val en = in(Bool) 49 | val we = in(Bits(generic.WRITE_DATA_WIDTH_A / generic.BYTE_WRITE_WIDTH_A bits)) 50 | val addr = in(UInt(generic.ADDR_WIDTH_A bits)) 51 | val din = in(Bits(generic.WRITE_DATA_WIDTH_A bits)) 52 | val injectsbiterr = in(Bool) default False 53 | val injectdbiterr = in(Bool) default False 54 | } 55 | val sleep = in(Bool) default False 56 | } 57 | 58 | addPrePopTask(() => { 59 | io.flatten.foreach(bt => { 60 | if (bt.getName().contains("write")) 61 | bt.setName(bt.getName().replace("write_", "") + "a") 62 | if (bt.getName().contains("read")) 63 | bt.setName(bt.getName().replace("read_", "") + "b") 64 | }) 65 | }) 66 | 67 | noIoPrefix 68 | mapClockDomain(clock = io.write.clk) 69 | mapClockDomain(clock = io.read.clk, reset = io.read.rst, resetActiveLevel = HIGH) 70 | } 71 | -------------------------------------------------------------------------------- /src/builder/MultiPipeline.scala: -------------------------------------------------------------------------------- 1 | package NOP.builder 2 | 3 | import NOP.builder._ 4 | 5 | import scala.collection.mutable 6 | import spinal.core._ 7 | import spinal.lib._ 8 | import scala.reflect.ClassTag 9 | 10 | trait MultiPipeline extends Pipeline { 11 | val pipelines = mutable.ArrayBuffer[Pipeline]() 12 | 13 | // override build function (which would throw errors for Pipeline) 14 | override def setUpPlugins(): Unit = { 15 | super.setUpPlugins() 16 | pipelines.foreach(_.setUpPlugins()) 17 | } 18 | 19 | override def buildPlugins(): Unit = { 20 | super.buildPlugins() 21 | pipelines.foreach(_.buildPlugins()) 22 | } 23 | 24 | override def connectStages(): Unit = { 25 | pipelines.foreach(_.connectStages()) 26 | } 27 | 28 | override def build(): Unit = { 29 | setUpPlugins() 30 | buildPlugins() 31 | connectStages() 32 | } 33 | 34 | def addPipeline(pipeline: Pipeline): Unit = { 35 | pipeline.setGlobalCtx(this) 36 | pipelines += pipeline 37 | } 38 | 39 | Component.current.addPrePopTask(() => build()) 40 | } 41 | -------------------------------------------------------------------------------- /src/builder/Plugin.scala: -------------------------------------------------------------------------------- 1 | // From vexriscv 2 | package NOP.builder 3 | 4 | import NOP.builder._ 5 | 6 | import spinal.core.{Area, Nameable} 7 | 8 | /** Created by PIC32F_USER on 03/03/2017. 9 | */ 10 | trait Plugin[T <: Pipeline] extends Nameable { 11 | var pipeline: T = null.asInstanceOf[T] 12 | setName(this.getClass.getSimpleName.replace("$", "")) 13 | 14 | // Used to setup things with other plugins 15 | def setup(pipeline: T): Unit = {} 16 | 17 | // Used to flush out the required hardware (called after setup) 18 | def build(pipeline: T): Unit 19 | 20 | implicit class implicitsStage(stage: Stage) { 21 | def plug[T <: Area](area: T): T = { area.setCompositeName(stage, getName()).reflectNames(); area } 22 | } 23 | implicit class implicitsPipeline(stage: Pipeline) { 24 | def plug[T <: Area](area: T) = { area.setName(getName()).reflectNames(); area } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/builder/Stage.scala: -------------------------------------------------------------------------------- 1 | // From https://github.dev/SpinalHDL/VexRiscv 2 | 3 | package NOP.builder 4 | 5 | import spinal.core._ 6 | import spinal.lib._ 7 | 8 | import scala.collection.mutable 9 | import scala.collection.mutable.ArrayBuffer 10 | 11 | class Stageable[T <: Data](_dataType: => T) extends HardType[T](_dataType) with Nameable { 12 | def dataType = apply() 13 | setWeakName(this.getClass.getSimpleName.replace("$", "")) 14 | } 15 | 16 | class Stage() extends Area { 17 | def outsideCondScope[T](that: => T): T = { 18 | val body = Component.current.dslBody // Get the head of the current component symboles tree (AST in other words) 19 | val ctx = body.push() // Now all access to the SpinalHDL API will be append to it (instead of the current context) 20 | val swapContext = body.swap() // Empty the symbole tree (but keep a reference to the old content) 21 | val ret = that // Execute the block of code (will be added to the recently empty body) 22 | ctx.restore() // Restore the original context in which this function was called 23 | swapContext.appendBack() // append the original symboles tree to the modified body 24 | ret // return the value returned by that 25 | } 26 | 27 | def input[T <: Data](key: Stageable[T]): T = { 28 | inputs 29 | .getOrElseUpdate( 30 | key.asInstanceOf[Stageable[Data]], 31 | outsideCondScope { 32 | val input, inputDefault = key() 33 | inputsDefault(key.asInstanceOf[Stageable[Data]]) = inputDefault 34 | input := inputDefault 35 | input.setPartialName(this, key.getName()) 36 | } 37 | ) 38 | .asInstanceOf[T] 39 | } 40 | 41 | def output[T <: Data](key: Stageable[T]): T = { 42 | outputs 43 | .getOrElseUpdate( 44 | key.asInstanceOf[Stageable[Data]], 45 | outsideCondScope { 46 | val output, outputDefault = key() 47 | outputsDefault(key.asInstanceOf[Stageable[Data]]) = outputDefault 48 | output := outputDefault 49 | output // .setPartialName(this,"output_" + key.getName()) 50 | } 51 | ) 52 | .asInstanceOf[T] 53 | } 54 | 55 | def insert[T <: Data](key: Stageable[T]): T = inserts 56 | .getOrElseUpdate(key.asInstanceOf[Stageable[Data]], outsideCondScope(key())) 57 | .asInstanceOf[T] // .setPartialName(this,key.getName()) 58 | // def apply[T <: Data](key : Stageable[T]) : T = ??? 59 | 60 | val arbitration = new Area { 61 | val haltItself = False // user settable, stuck the instruction, should only be set by the instruction itself 62 | val haltByOther = 63 | False // When settable, stuck the instruction, should only be set by something else than the stucked instruction 64 | val removeIt = False // When settable, unschedule the instruction as if it was never executed (no side effect) 65 | val flushIt = False // When settable, unschedule the current instruction 66 | val flushNext = False // When settable, unschedule instruction above in the pipeline 67 | val isValid = Bool // Inform if a instruction is in the current stage 68 | val isValidOnEntry = Bool // Inform if an instruction is valid when entering the current stage 69 | val isStuck = Bool // Inform if the instruction is stuck (haltItself || haltByOther) 70 | val notStuck = !isStuck 71 | val isStuckByOthers = Bool // Inform if the instruction is stuck by sombody else 72 | val isRemoved = removeIt // Inform if the instruction is going to be unschedule the current cycle 73 | val isFlushed = Bool // Inform if the instruction is flushed (flushAll set in the current or subsequents stages) 74 | val isMoving = Bool // Inform if the instruction is going somewere else (next stage or unscheduled) 75 | val isFiring = 76 | Bool // Inform if the current instruction will go to the next stage the next cycle (isValid && !isStuck && !removeIt) 77 | def isValidNotStuck: Bool = isValid && !isStuck 78 | } 79 | 80 | val inputs = mutable.LinkedHashMap[Stageable[Data], Data]() 81 | val outputs = mutable.LinkedHashMap[Stageable[Data], Data]() 82 | val signals = mutable.LinkedHashMap[Stageable[Data], Data]() 83 | val inserts = mutable.LinkedHashMap[Stageable[Data], Data]() 84 | 85 | val inputsDefault = mutable.LinkedHashMap[Stageable[Data], Data]() 86 | val outputsDefault = mutable.LinkedHashMap[Stageable[Data], Data]() 87 | 88 | val dontSample = mutable.LinkedHashMap[Stageable[_], ArrayBuffer[Bool]]() 89 | 90 | def dontSampleStageable(s: Stageable[_], cond: Bool): Unit = { 91 | dontSample.getOrElseUpdate(s, ArrayBuffer[Bool]()) += cond 92 | } 93 | def inputInit[T <: BaseType](stageable: Stageable[T], initValue: T) = 94 | Component.current.addPrePopTask(() => 95 | inputsDefault(stageable.asInstanceOf[Stageable[Data]]).asInstanceOf[T].getDrivingReg().init(initValue) 96 | ) 97 | } 98 | -------------------------------------------------------------------------------- /src/constants/LoongArch.scala: -------------------------------------------------------------------------------- 1 | package NOP.constants 2 | 3 | import NOP._ 4 | import NOP.builder._ 5 | import NOP.constants.LoongArch.Bits 6 | import NOP.utils._ 7 | import spinal.core._ 8 | import spinal.lib._ 9 | 10 | // LoongArch Instruction Set Architecture 11 | object LoongArch { 12 | private def Rk(): String = "-----" 13 | private def Rj(): String = "-----" 14 | private def Rd(): String = "-----" 15 | private def Bits(len: Integer): String = "-" * len 16 | 17 | private def MakeMaskedLiteral(mask: String): MaskedLiteral = { 18 | // Replace spaces with nothing 19 | val replaced_mask = mask.replace(" ", "") 20 | assert(replaced_mask.length == 32) 21 | MaskedLiteral(replaced_mask) 22 | } 23 | 24 | // Instruction Masks 25 | val ADD = MakeMaskedLiteral("00000000000100000" + Rk() + Rj() + Rd()) 26 | val SUB = MakeMaskedLiteral("00000000000100010" + Rk() + Rj() + Rd()) 27 | val SLT = MakeMaskedLiteral("00000000000100100" + Rk() + Rj() + Rd()) 28 | val SLTU = MakeMaskedLiteral("00000000000100101" + Rk() + Rj() + Rd()) 29 | val NOR = MakeMaskedLiteral("00000000000101000" + Rk() + Rj() + Rd()) 30 | val AND = MakeMaskedLiteral("00000000000101001" + Rk() + Rj() + Rd()) 31 | val OR = MakeMaskedLiteral("00000000000101010" + Rk() + Rj() + Rd()) 32 | val XOR = MakeMaskedLiteral("00000000000101011" + Rk() + Rj() + Rd()) 33 | val SLL = MakeMaskedLiteral("00000000000101110" + Rk() + Rj() + Rd()) 34 | val SRL = MakeMaskedLiteral("00000000000101111" + Rk() + Rj() + Rd()) 35 | val SRA = MakeMaskedLiteral("00000000000110000" + Rk() + Rj() + Rd()) 36 | 37 | val SLLI = MakeMaskedLiteral("0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1" + Bits(5) + Rj() + Rd()) 38 | val SRLI = MakeMaskedLiteral("0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 1" + Bits(5) + Rj() + Rd()) 39 | val SRAI = MakeMaskedLiteral("0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 1" + Bits(5) + Rj() + Rd()) 40 | 41 | val SLTI = MakeMaskedLiteral("0000001000" + Bits(12) + Rj() + Rd()) 42 | val SLTUI = MakeMaskedLiteral("0000001001" + Bits(12) + Rj() + Rd()) 43 | val ADDI = MakeMaskedLiteral("0000001010" + Bits(12) + Rj() + Rd()) 44 | val ANDI = MakeMaskedLiteral("0000001101" + Bits(12) + Rj() + Rd()) 45 | val ORI = MakeMaskedLiteral("0000001110" + Bits(12) + Rj() + Rd()) 46 | val XORI = MakeMaskedLiteral("0000001111" + Bits(12) + Rj() + Rd()) 47 | 48 | val LU12I = MakeMaskedLiteral("0 0 0 1 0 1 0" + Bits(20) + Rd()) 49 | val PCADDI = MakeMaskedLiteral("0 0 0 1 1 0 0" + Bits(20) + Rd()) 50 | val PCADDU12I = MakeMaskedLiteral("0 0 0 1 1 1 0" + Bits(20) + Rd()) 51 | 52 | val JIRL = MakeMaskedLiteral("0 1 0 0 1 1" + Bits(16) + Rj() + Rd()) 53 | val B = MakeMaskedLiteral("0 1 0 1 0 0" + Bits(16) + Bits(10)) 54 | val BL = MakeMaskedLiteral("0 1 0 1 0 1" + Bits(16) + Bits(10)) 55 | val BEQ = MakeMaskedLiteral("010110" + Bits(16) + Rj() + Rd()) 56 | val BNE = MakeMaskedLiteral("010111" + Bits(16) + Rj() + Rd()) 57 | val BLT = MakeMaskedLiteral("011000" + Bits(16) + Rj() + Rd()) 58 | val BGE = MakeMaskedLiteral("011001" + Bits(16) + Rj() + Rd()) 59 | val BLTU = MakeMaskedLiteral("011010" + Bits(16) + Rj() + Rd()) 60 | val BGEU = MakeMaskedLiteral("011011" + Bits(16) + Rj() + Rd()) 61 | 62 | val MUL = MakeMaskedLiteral("00000000000111000" + Rk() + Rj() + Rd()) 63 | val MULH = MakeMaskedLiteral("00000000000111001" + Rk() + Rj() + Rd()) 64 | val MULHU = MakeMaskedLiteral("00000000000111010" + Rk() + Rj() + Rd()) 65 | val DIV = MakeMaskedLiteral("00000000001000000" + Rk() + Rj() + Rd()) 66 | val MOD = MakeMaskedLiteral("00000000001000001" + Rk() + Rj() + Rd()) 67 | val DIVU = MakeMaskedLiteral("00000000001000010" + Rk() + Rj() + Rd()) 68 | val MODU = MakeMaskedLiteral("00000000001000011" + Rk() + Rj() + Rd()) 69 | 70 | val LDB = MakeMaskedLiteral("0010100000" + Bits(12) + Rj() + Rd()) // 21 downto 10 71 | val LDBU = MakeMaskedLiteral("0010101000" + Bits(12) + Rj() + Rd()) 72 | val LDH = MakeMaskedLiteral("0010100001" + Bits(12) + Rj() + Rd()) 73 | val LDHU = MakeMaskedLiteral("0010101001" + Bits(12) + Rj() + Rd()) 74 | val LDW = MakeMaskedLiteral("0010100010" + Bits(12) + Rj() + Rd()) 75 | val STB = MakeMaskedLiteral("0010100100" + Bits(12) + Rj() + Rd()) 76 | val STH = MakeMaskedLiteral("0010100101" + Bits(12) + Rj() + Rd()) 77 | val STW = MakeMaskedLiteral("0010100110" + Bits(12) + Rj() + Rd()) 78 | 79 | val ERTN = MakeMaskedLiteral("00000110010010000011100000000000") 80 | val BREAK = MakeMaskedLiteral("0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 0" + Bits(15)) 81 | val SYSCALL = MakeMaskedLiteral("0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 1 0" + Bits(15)) 82 | val CSR = MakeMaskedLiteral("0 0 0 0 0 1 0 0" + Bits(14) + Rj() + Rd()) 83 | val IDLE = MakeMaskedLiteral("0 0 0 0 0 1 1 0 0 1 0 0 1 0 0 0 1" + Bits(15)) 84 | 85 | val RDCNTVL = MakeMaskedLiteral("0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0" + Rj() + Rd()) 86 | val RDCNTVH = MakeMaskedLiteral("0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1" + "0 0 0 0 0" + Rd()) 87 | 88 | val CACOP = MakeMaskedLiteral("0 0 0 0 0 1 1 0 0 0" + Bits(12) + Rj() + Bits(5)) // 4 downto 0 89 | val PRELD = MakeMaskedLiteral("0 0 1 0 1 0 1 0 1 1" + Bits(12) + Rj() + Bits(5)) // Not used :) 90 | val DBAR = MakeMaskedLiteral("0 0 1 1 1 0 0 0 0 1 1 1 0 0 1 0 0" + Bits(15)) 91 | val IBAR = MakeMaskedLiteral("0 0 1 1 1 0 0 0 0 1 1 1 0 0 1 0 1" + Bits(15)) 92 | 93 | val LL = MakeMaskedLiteral("0 0 1 0 0 0 0 0" + Bits(14) + Rj() + Rd()) 94 | val SC = MakeMaskedLiteral("0 0 1 0 0 0 0 1" + Bits(14) + Rj() + Rd()) 95 | 96 | val TLBSRCH = MakeMaskedLiteral("00000110010010000010100000000000") 97 | val TLBRD = MakeMaskedLiteral("00000110010010000010110000000000") 98 | val TLBWR = MakeMaskedLiteral("00000110010010000011000000000000") 99 | val TLBFILL = MakeMaskedLiteral("00000110010010000011010000000000") 100 | val INVTLB = MakeMaskedLiteral("0 0 0 0 0 1 1 0 0 1 0 0 1 0 0 1 1" + Rk() + Rj() + Bits(5)) 101 | 102 | object CSRAddress { 103 | val CRMD = 0x0 104 | val PRMD = 0x1 105 | val EUEN = 0x2 106 | val ECFG = 0x4 107 | val ESTAT = 0x5 108 | val ERA = 0x6 109 | val BADV = 0x7 110 | val EENTRY = 0xc 111 | val TLBIDX = 0x10 112 | val TLBEHI = 0x11 113 | val TLBELO0 = 0x12 114 | val TLBELO1 = 0x13 115 | val ASID = 0x18 116 | val PGDL = 0x19 117 | val PGDH = 0x1a 118 | val PGD = 0x1b 119 | val CPUID = 0x20 120 | val SAVE0 = 0x30 121 | val SAVE1 = 0x31 122 | val SAVE2 = 0x32 123 | val SAVE3 = 0x33 124 | val TID = 0x40 125 | val TCFG = 0x41 126 | val TVAL = 0x42 127 | val TICLR = 0x44 128 | val LLBCTL = 0x60 129 | val TLBRENTRY = 0x88 130 | val CTAG = 0x98 131 | val DMW0 = 0x180 132 | val DMW1 = 0x181 133 | } 134 | 135 | object ExceptionCode { 136 | case class Exception(ecode: Int, esubcode: Int) {} 137 | 138 | def ECode(x: Int) = x 139 | def EsubCode(x: Int) = x 140 | 141 | val INT = Exception(ECode(0x0), EsubCode(0)) 142 | val PIL = Exception(ECode(0x1), EsubCode(0)) // Load 操作页无效 143 | val PIS = Exception(ECode(0x2), EsubCode(0)) // Store 操作页无效 144 | val PIF = Exception(ECode(0x3), EsubCode(0)) // 取指操作页无效 145 | val PME = Exception(ECode(0x4), EsubCode(0)) // 页修改例外 146 | val PPI = Exception(ECode(0x7), EsubCode(0)) // 页特权例外 147 | val ADEF = Exception(ECode(0x8), EsubCode(0)) // 地址错 (Fetch) 148 | val ADEM = Exception(ECode(0x8), EsubCode(1)) // 地址错 (Load/Store) 149 | val ALE = Exception(ECode(0x9), EsubCode(0)) // 地址不对齐例外 150 | val SYS = Exception(ECode(0xb), EsubCode(0)) // 系统调用例外 151 | val BRK = Exception(ECode(0xc), EsubCode(0)) // 断点例外 152 | val INE = Exception(ECode(0xd), EsubCode(0)) // 指令非法例外 153 | val IPE = Exception(ECode(0xe), EsubCode(0)) // 指令特权例外 154 | val FPD = Exception(ECode(0xf), EsubCode(0)) // 浮点例外, not implemented 155 | val FPE = Exception(ECode(0x12), EsubCode(1)) // 浮点例外, not implemented 156 | val TLBR = Exception(ECode(0x3f), EsubCode(0)) // TLB Refill 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/constants/enum/ALUOpType.scala: -------------------------------------------------------------------------------- 1 | package NOP.constants.enum 2 | 3 | import spinal.core.SpinalEnum 4 | 5 | object ALUOpType extends SpinalEnum { 6 | val ADD, ADDU, SUB, SUBU = newElement() 7 | val AND, OR, XOR, NOR = newElement() 8 | val SLT, SLTU = newElement() 9 | val SLL, SRL, SRA = newElement() 10 | val LU12I, PCADDI, PCADDU12I = newElement() 11 | // val CLO, CLZ = newElement() 12 | } 13 | -------------------------------------------------------------------------------- /src/constants/enum/CacheOpType.scala: -------------------------------------------------------------------------------- 1 | package NOP.constants.enum 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP.utils._ 7 | import spinal.core.SpinalEnum 8 | 9 | object CacheSelType extends SpinalEnum { 10 | val None, ICache, DCache, SharedCache = newElement() 11 | } 12 | 13 | object CacheOpType extends SpinalEnum { 14 | val None = newElement() 15 | val StoreTag = newElement() 16 | val IndexInvalidate = newElement() 17 | val HitInvalidate = newElement() 18 | } 19 | -------------------------------------------------------------------------------- /src/constants/enum/CompareOpType.scala: -------------------------------------------------------------------------------- 1 | package NOP.constants.enum 2 | 3 | import spinal.core.Spinal 4 | import spinal.core.SpinalEnum 5 | 6 | object CompareOpType extends SpinalEnum { 7 | val EQ, NE = newElement() 8 | val EQZ, NEZ = newElement() 9 | val GE, LT, LE, GT = newElement() 10 | val GEU, LTU, LEU, GTU = newElement() 11 | val GEZ, LTZ, LEZ, GTZ = newElement() 12 | } 13 | -------------------------------------------------------------------------------- /src/constants/enum/ExtendType.scala: -------------------------------------------------------------------------------- 1 | package NOP.constants.enum 2 | 3 | import spinal.core.SpinalEnum 4 | 5 | object ExtendType extends SpinalEnum { 6 | val SIGN, ZERO = newElement() 7 | } 8 | -------------------------------------------------------------------------------- /src/constants/enum/FUType.scala: -------------------------------------------------------------------------------- 1 | package NOP.constants.enum 2 | 3 | import spinal.core.SpinalEnum 4 | 5 | /** Function unit type. 6 | */ 7 | object FUType extends SpinalEnum { 8 | val NONE = newElement() 9 | val ALU, CMP, CSR, TIMER, INVTLB = newElement() 10 | val MUL, MULH, DIV, MOD = newElement() 11 | val LSU = newElement() 12 | } 13 | -------------------------------------------------------------------------------- /src/constants/enum/LoadStoreType.scala: -------------------------------------------------------------------------------- 1 | package NOP.constants.enum 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import spinal.core.SpinalEnum 6 | 7 | object LoadStoreType extends SpinalEnum { 8 | val BYTE, HALF, WORD, BYTE_U, HALF_U = newElement() 9 | val CACOP = newElement() 10 | val PRELD = newElement() 11 | 12 | def toAxiSize(lsType: SpinalEnumCraft[LoadStoreType.type]): UInt = { 13 | val size = UInt(3 bits) 14 | switch(lsType) { 15 | is(BYTE, BYTE_U) { 16 | size := 0 17 | } 18 | is(HALF, HALF_U) { 19 | size := 1 20 | } 21 | default { 22 | size := 2 23 | } 24 | } 25 | size 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/constants/enum/MemOperationType.scala: -------------------------------------------------------------------------------- 1 | package NOP.constants.enum 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import spinal.core.SpinalEnum 7 | 8 | object MemOperationType extends SpinalEnum { 9 | val FETCH, LOAD, STORE = newElement() 10 | } 11 | -------------------------------------------------------------------------------- /src/constants/enum/PrivModeType.scala: -------------------------------------------------------------------------------- 1 | package NOP.constants 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import spinal.core.SpinalEnum 7 | 8 | object PrivModeType extends SpinalEnum { 9 | val PLV0 = newElement() 10 | val PLV3 = newElement() 11 | } 12 | -------------------------------------------------------------------------------- /src/constants/enum/TLBOpType.scala: -------------------------------------------------------------------------------- 1 | package NOP.constants.enum 2 | 3 | import spinal.core.SpinalEnum 4 | 5 | object TLBOpType extends SpinalEnum { 6 | val NONE, TLBSRCH, TLBRD, TLBWR, TLBFILL = newElement() 7 | val INVTLB1, INVTLB2, INVTLB3, INVTLB4, INVTLB5, INVTLB6 = newElement() 8 | } 9 | -------------------------------------------------------------------------------- /src/debug/DebugInterface.scala: -------------------------------------------------------------------------------- 1 | // Copied from Zencove::DebugInterface 2 | // Used for debugging purposes 3 | package NOP.debug 4 | 5 | import spinal.core._ 6 | import spinal.lib._ 7 | 8 | class DebugRegFile() extends Bundle { 9 | val wen = Bits(4 bits) 10 | val wnum = UInt(5 bits) 11 | val wdata = Bits(32 bits) 12 | } 13 | 14 | class DebugWriteback() extends Bundle { 15 | val pc = UInt(32 bits) 16 | val rf = new DebugRegFile() 17 | val inst = Bits(32 bits) 18 | } 19 | 20 | class DebugInterface() extends Bundle { 21 | val wb = new DebugWriteback() 22 | } 23 | -------------------------------------------------------------------------------- /src/peripheral/AxiBuffer.scala: -------------------------------------------------------------------------------- 1 | package NOP.peripheral 2 | 3 | import spinal.core._ 4 | import spinal.lib.bus.amba4.axi._ 5 | import spinal.lib._ 6 | import spinal.lib.fsm._ 7 | 8 | import NOP.blackbox.mem._ 9 | 10 | case class BufferEntry(config: Axi4Config) extends Bundle { 11 | val id = UInt(config.idWidth bits) 12 | val addr = UInt(config.addressWidth bits) 13 | val data = Bits(config.dataWidth bits) 14 | val size = UInt(3 bits) 15 | val strb = config.useStrb generate Bits(config.dataWidth / 8 bits) 16 | } 17 | 18 | class AxiBuffer(axiConfig: Axi4Config) extends Component { 19 | val io = new Bundle { 20 | val in_axi = slave(Axi4(axiConfig)) 21 | val out_axi = master(Axi4(axiConfig)) 22 | } 23 | val bufferType = HardType(BufferEntry(axiConfig)) 24 | val dataWidth = bufferType.getBitsWidth 25 | 26 | val generic = new xpm_fifo_sync_generic() 27 | generic.READ_MODE = "std" 28 | generic.FIFO_WRITE_DEPTH = 1024 29 | generic.WRITE_DATA_WIDTH = dataWidth 30 | generic.READ_DATA_WIDTH = dataWidth 31 | 32 | val fifo = new xpm_fifo_sync(generic) 33 | fifo.io.rd_en := False 34 | fifo.io.wr_en := False 35 | fifo.io.din := B(0, dataWidth bits) 36 | 37 | io.in_axi.setBlocked() 38 | io.out_axi.setIdle() 39 | io.out_axi.b.ready.allowOverride := True 40 | io.in_axi.b.payload.resp.allowOverride := B(0, 2 bits) // OKAY 41 | 42 | val outaw = io.out_axi.aw 43 | val outw = io.out_axi.w 44 | val outb = io.out_axi.b 45 | val outar = io.out_axi.ar 46 | val outr = io.out_axi.r 47 | 48 | val inaw = io.in_axi.aw 49 | val inw = io.in_axi.w 50 | val inb = io.in_axi.b 51 | val inar = io.in_axi.ar 52 | val inr = io.in_axi.r 53 | 54 | val writeIsRunning = False 55 | 56 | val readOutputFSM = new StateMachine { 57 | disableAutoStart() 58 | setEntry(state = stateBoot) 59 | 60 | val waitR = new State 61 | 62 | stateBoot.whenIsActive { 63 | when(fifo.io.empty && inar.valid && !writeIsRunning) { 64 | inar <> outar 65 | inr <> outr 66 | goto(waitR) 67 | } 68 | } 69 | 70 | waitR.whenIsActive { 71 | inar <> outar 72 | inr <> outr 73 | when(inr.payload.last && inr.valid && inr.ready) { 74 | goto(stateBoot) 75 | } 76 | } 77 | } 78 | 79 | when(fifo.io.wr_rst_busy || fifo.io.rd_rst_busy || fifo.io.rst) { 80 | // Do nothing when rst is busy 81 | } elsewhen (!fifo.io.full && inaw.valid && !fifo.io.rd_en) { 82 | val entry = bufferType() 83 | 84 | fifo.io.wr_en := True 85 | fifo.io.din := entry.asBits 86 | 87 | inaw.ready := True 88 | inw.ready := True 89 | inb.valid := True 90 | // NOTE: !!! 91 | // udBus characteristics 92 | // 1. aw and w are valid at the same time 93 | // 2. len = 0 94 | entry.addr := inaw.payload.addr 95 | entry.size := inaw.payload.size 96 | entry.id := inaw.payload.id 97 | entry.data := inw.payload.data 98 | if (axiConfig.useStrb) 99 | entry.strb := inw.payload.strb 100 | } 101 | 102 | val writeOutputFSM = new StateMachine { 103 | disableAutoStart() 104 | setEntry(state = stateBoot) 105 | 106 | val waitAW = new State 107 | val waitRead = new State 108 | val waitW = new State 109 | val waitB = new State 110 | 111 | val regEntry = Reg(bufferType) 112 | 113 | stateBoot.whenIsActive { 114 | when(fifo.io.rd_rst_busy) { 115 | // Do nothing when rst is busy 116 | } otherwise { 117 | when(!fifo.io.empty) { 118 | writeIsRunning := True 119 | fifo.io.rd_en := True 120 | goto(waitRead) 121 | } 122 | } 123 | } 124 | 125 | waitRead.whenIsActive { 126 | writeIsRunning := True 127 | 128 | val entry = bufferType() 129 | entry.assignFromBits(fifo.io.dout) 130 | regEntry := entry 131 | 132 | outaw.valid := True 133 | outaw.payload.id := entry.id 134 | outaw.payload.addr := entry.addr 135 | outaw.len := 0 // 1 transfer only 136 | outaw.size := entry.size 137 | outaw.burst := 1 // increment 138 | 139 | outw.valid := True 140 | outw.payload.data := entry.data 141 | outw.payload.last := True 142 | if (axiConfig.useStrb) 143 | outw.payload.strb := entry.strb 144 | goto(waitAW) 145 | } 146 | 147 | waitAW.whenIsActive { 148 | writeIsRunning := True 149 | outaw.valid := True 150 | outaw.payload.id := regEntry.id 151 | outaw.payload.addr := regEntry.addr 152 | outaw.len := 0 // 1 transfer only 153 | outaw.size := regEntry.size 154 | outaw.burst := 1 // increment 155 | 156 | outw.valid := True 157 | outw.payload.data := regEntry.data 158 | outw.payload.last := True 159 | if (axiConfig.useStrb) 160 | outw.payload.strb := regEntry.strb 161 | when(outaw.ready && outw.ready) { 162 | when(outb.valid) { 163 | goto(stateBoot); 164 | } otherwise { 165 | goto(waitB); 166 | } 167 | } elsewhen (outaw.ready) { 168 | goto(waitW); 169 | } 170 | } 171 | 172 | waitW.whenIsActive { 173 | writeIsRunning := True 174 | outw.valid := True 175 | outw.payload.data := regEntry.data 176 | outw.payload.last := True 177 | if (axiConfig.useStrb) 178 | outw.payload.strb := regEntry.strb 179 | when(outw.ready && outb.valid) { 180 | goto(stateBoot) 181 | } elsewhen (outw.ready) { 182 | goto(waitB) 183 | } 184 | } 185 | 186 | waitB.whenIsActive { 187 | writeIsRunning := True 188 | when(outb.valid) { 189 | goto(stateBoot) 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/peripheral/AxiCrossbar.scala: -------------------------------------------------------------------------------- 1 | package NOP.peripheral 2 | 3 | import spinal.core._ 4 | import spinal.lib.bus.amba4.axi._ 5 | import spinal.lib._ 6 | 7 | class AxiCrossbar(axiConfig: Axi4Config) extends Component { 8 | val io = new Bundle { 9 | val iBus, dBus, udBus = slave(Axi4(axiConfig)).setBlocked() 10 | val cpuBus = master(Axi4(axiConfig)).setIdle() 11 | } 12 | 13 | val writeArb = new Area { 14 | val busList = Seq(io.dBus, io.udBus) 15 | val busWithIndex = busList.zipWithIndex 16 | val busy = RegInit(False) 17 | val grantId = Reg(UInt(1 bits)) 18 | val hasReq = busList.map(_.aw.valid).orR 19 | when(!busy && hasReq) { 20 | busy := True 21 | for ((bus, i) <- busWithIndex) when(bus.aw.valid) { grantId := i } 22 | } 23 | when(busy) { 24 | for ((bus, i) <- busWithIndex) when(grantId === i) { 25 | bus.aw >> io.cpuBus.aw 26 | bus.w >> io.cpuBus.w 27 | bus.b << io.cpuBus.b 28 | } 29 | } 30 | busy.clearWhen(io.cpuBus.b.fire) 31 | } 32 | 33 | val readArb = new Area { 34 | val busList = Seq(io.iBus, io.dBus, io.udBus) 35 | val busWithIndex = busList.zipWithIndex 36 | val busy = RegInit(False) 37 | val grantId = Reg(UInt(2 bits)) 38 | val hasReq = busList.map(_.ar.valid).orR 39 | when(!busy && hasReq) { 40 | busy := True 41 | for ((bus, i) <- busWithIndex) when(bus.ar.valid) { grantId := i } 42 | } 43 | when(busy) { 44 | for ((bus, i) <- busWithIndex) when(grantId === i) { 45 | bus.ar >> io.cpuBus.ar 46 | bus.r << io.cpuBus.r 47 | } 48 | } 49 | busy.clearWhen(io.cpuBus.r.fire && io.cpuBus.r.last) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/pipeline/core/BypassNetworkPlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.core 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP.builder._ 7 | import scala.collection.mutable 8 | import NOP._ 9 | import NOP.utils._ 10 | import NOP.constants.enum._ 11 | import NOP.pipeline.core._ 12 | 13 | class BypassNetworkPlugin(config: RegFileConfig) extends Plugin[MyCPUCore] { 14 | val writePorts = mutable.ArrayBuffer[Flow[RegWriteBundle]]() 15 | private val readPorts = mutable.ArrayBuffer[(UInt, Flow[Bits])]() 16 | 17 | def writePort = { 18 | val port = Flow(RegWriteBundle(config.prfAddrWidth)) 19 | writePorts += port 20 | port 21 | } 22 | 23 | def readPort(addr: UInt) = { 24 | val port = Flow(BWord()).setIdle() 25 | readPorts += (addr -> port) 26 | port 27 | } 28 | 29 | override def build(pipeline: MyCPUCore): Unit = { 30 | // 前传逻辑 31 | readPorts.foreach { r => 32 | // r._1 是地址,r._2 是数据 33 | // p0 永远不应该被写,它是 r0 的固定映射 34 | // 永远不应该出现写冲突,rename处理掉了写冲突 35 | val writeOH = writePorts.map { p => p.valid && p.payload.addr === r._1 } 36 | when(writeOH.orR) { r._2.push(MuxOH(writeOH, writePorts.map(_.payload.data))) } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/pipeline/core/CommitTraits.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.core 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP._ 7 | import NOP.utils._ 8 | import NOP.constants.enum._ 9 | import NOP.builder._ 10 | import NOP.pipeline._ 11 | import NOP.pipeline.decode._ 12 | import NOP.pipeline.fetch._ 13 | import NOP.pipeline.core._ 14 | import NOP.pipeline.priviledge._ 15 | 16 | trait ExceptionCommit { 17 | val except = out(Flow(ExceptionPayloadBundle(true))) 18 | val ertn = out Bool () 19 | val epc = out(UWord()) 20 | } 21 | 22 | trait TLBCommit { 23 | val tlbOp = TLBOpType() 24 | val tlbInvASID = Bits(10 bits) 25 | val tlbInvVPPN = Bits(19 bits) 26 | } 27 | 28 | trait CacheCommit { 29 | val cacheOp = out(Flow(CacheOperation())) 30 | } 31 | 32 | /** Commit to branch prediction unit. 33 | */ 34 | trait BPUCommit { 35 | val predUpdate: Flow[PredictUpdateBundle] 36 | } 37 | 38 | trait CSRCommit { 39 | val CSRWrite = out(Flow(CSRWriteBundle())) 40 | } 41 | 42 | trait ARFCommit { 43 | val arfCommits: Vec[Flow[RegFileMappingEntryBundle]] 44 | val recoverPRF = Bool() 45 | } 46 | 47 | trait WaitCommit { 48 | val doWait = Bool() 49 | } 50 | 51 | trait StoreBufferCommit { 52 | val commitStore = Bool() 53 | } 54 | 55 | trait CommitFlush { 56 | val needFlush = Bool 57 | val regFlush = RegNext(needFlush, init = False) 58 | } 59 | -------------------------------------------------------------------------------- /src/pipeline/core/ExceptionMuxPlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.core 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import NOP.constants.LoongArch 6 | import NOP.utils._ 7 | import NOP.builder._ 8 | import scala.collection.mutable 9 | import scala.math 10 | 11 | class ExceptionMuxPlugin[T <: Pipeline](lastInsertIdx: Int) extends Plugin[T] { 12 | case class ExceptionSource(valid: Bool, badVAddr: UInt, exc: LoongArch.ExceptionCode.Exception, priority: Int) 13 | private val excMap = mutable.HashMap[Stage, mutable.ArrayBuffer[ExceptionSource]]() 14 | def addExceptionSource( 15 | stage: Stage, 16 | exc: LoongArch.ExceptionCode.Exception, 17 | valid: Bool, 18 | badVAddr: UInt = null, 19 | priority: Int = 0 20 | ) { 21 | excMap.getOrElseUpdate(stage, mutable.ArrayBuffer()) += 22 | ExceptionSource(valid, badVAddr, exc, priority) 23 | } 24 | 25 | object ExceptionSignals { 26 | object BAD_VADDR extends Stageable(UWord()) 27 | object EXCEPTION_ECODE extends Stageable(Bits(6 bits)) 28 | object EXCEPTION_ESUBCODE extends Stageable(Bits(9 bits)) 29 | object EXCEPTION_OCCURRED extends Stageable(Bool()) 30 | } 31 | 32 | override def build(pipeline: T): Unit = pipeline plug new Area { 33 | 34 | import ExceptionSignals._ 35 | 36 | // insert exception signals for handler, even if no exception happens 37 | var firstExcStage = lastInsertIdx 38 | var firstVAStage = lastInsertIdx 39 | excMap.foreach { case (stage, excList) => 40 | if (excList.nonEmpty) 41 | firstExcStage = math.min(firstExcStage, pipeline.indexOf(stage)) 42 | excList.sortBy(_.priority).foreach { excSrc => 43 | stage.output(EXCEPTION_OCCURRED).setWhen(excSrc.valid) 44 | // 如果先前已经有异常,那么忽略本阶段产生的异常 45 | when(!stage.input(EXCEPTION_OCCURRED) && excSrc.valid) { 46 | stage.output(EXCEPTION_ECODE) := U(excSrc.exc.ecode, 6 bits).asBits 47 | stage.output(EXCEPTION_ESUBCODE) := U(excSrc.exc.esubcode, 9 bits).asBits 48 | // badVA仅在需要时修改 49 | if (excSrc.badVAddr != null) { 50 | firstVAStage = math.min(firstVAStage, pipeline.indexOf(stage)) 51 | stage.output(BAD_VADDR) := excSrc.badVAddr 52 | } 53 | } 54 | } 55 | } 56 | // 初值在需要的阶段insert 57 | pipeline.stages(firstExcStage).insert(EXCEPTION_OCCURRED) := False 58 | pipeline.stages(firstExcStage).insert(EXCEPTION_ECODE).assignDontCare() 59 | pipeline.stages(firstExcStage).insert(EXCEPTION_ESUBCODE).assignDontCare() 60 | pipeline.stages(firstVAStage).insert(BAD_VADDR).assignDontCare() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/pipeline/core/MyCPUCore.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.core 2 | 3 | import NOP._ 4 | import NOP.debug._ 5 | import NOP.builder._ 6 | import NOP.pipeline._ 7 | import NOP.pipeline.fetch._ 8 | import NOP.pipeline.decode._ 9 | import NOP.pipeline.exe._ 10 | import NOP.pipeline.mem._ 11 | import NOP.pipeline.priviledge._ 12 | import spinal.core._ 13 | import spinal.lib.bus.amba4.axi._ 14 | import spinal.lib._ 15 | 16 | class MyCPUCore(config: MyCPUConfig) extends Component with MultiPipeline { 17 | 18 | val global = this 19 | 20 | // ! Fetch Pipeline 21 | val fetchPipeline = new FetchPipeline { 22 | 23 | override val signals = new FetchSignals(config) 24 | override val IF1: Stage = newStage() 25 | override val IF2: Stage = newStage() 26 | 27 | plugins ++= List( 28 | new ProgramCounterPlugin(config.frontend), 29 | new FetchBufferPlugin(config), 30 | new ICachePlugin(config), 31 | new ExceptionMuxPlugin[FetchPipeline](stages.size - 1), 32 | new InstAddrTranslatePlugin(), 33 | new GlobalPredictorBTBPlugin(config.frontend), 34 | new ReturnAddressStackPlugin(config.frontend) 35 | ).filter(_ != null) 36 | 37 | } 38 | addPipeline(fetchPipeline) 39 | 40 | // ! Decode Pipeline 41 | val decodePipeline: DecodePipeline = new DecodePipeline { 42 | 43 | override val signals = new DecodeSignals(config) 44 | override val ID: Stage = newStage() 45 | override val RENAME: Stage = newStage() 46 | override val DISPATCH: Stage = newStage() 47 | 48 | plugins ++= List( 49 | new DecoderArray(config, fetchPipeline.service(classOf[FetchBufferPlugin]).popPorts), 50 | new RenamePlugin(config) 51 | ).filter(_ != null) 52 | } 53 | addPipeline(decodePipeline) 54 | 55 | // val memPipeline: MemPipeline = null 56 | 57 | // ! INT Pipeline 58 | var exePipelines = Vector[ExecutePipeline]() 59 | for (i <- (0 until config.intIssue.issueWidth).reverse) { 60 | val exePipeline = new ExecutePipeline { 61 | override val ISS: Stage = newStage().setName(s"INT${i}_ISS") 62 | override val RRD: Stage = newStage().setName(s"INT${i}_RRD") 63 | override val EXE: Stage = newStage().setName(s"INT${i}_EXE") 64 | override val WB: Stage = newStage().setName(s"INT${i}_WB") 65 | plugins += new IntExecutePlugin(config, i) 66 | } 67 | exePipelines = exePipelines :+ exePipeline 68 | addPipeline(exePipeline) 69 | } 70 | 71 | // ! Mul / Div Pipeline 72 | val mulDivPipeline = new ExecutePipeline { 73 | override val ISS = newStage().setName("MULDIV_ISS") 74 | override val RRD = newStage().setName("MULDIV_RRD") 75 | override val EXE = newStage().setName("MULDIV_EXE") 76 | override val WB = newStage().setName("MULDIV_WB") 77 | plugins += new MulDivExecutePlugin(config) 78 | } 79 | addPipeline(mulDivPipeline) 80 | 81 | // ! Mem Pipeline 82 | val memPipeline = new MemPipeline { 83 | override val signals = new MemSignals(config) 84 | override val ISS: Stage = newStage().setName("MEM_ISS") 85 | override val RRD: Stage = newStage().setName("MEM_RRD") 86 | override val MEMADDR: Stage = newStage().setName("MEM_ADDR") 87 | override val MEM1: Stage = newStage().setName("MEM_MEM1") 88 | override val MEM2: Stage = newStage().setName("MEM_MEM2") 89 | override val WB: Stage = newStage().setName("MEM_WB") 90 | override val WB2: Stage = newStage().setName("MEM_WB2") 91 | 92 | plugins ++= List( 93 | new AddressGenerationPlugin(config), 94 | new DCachePlugin(config), 95 | new UncachedAccessPlugin(config), 96 | new LoadPostprocessPlugin(), 97 | new StoreBufferPlugin(config), 98 | new MemExecutePlugin(config), 99 | new ExceptionMuxPlugin[MemPipeline](stages.size - 1) 100 | ).filter(_ != null) 101 | } 102 | addPipeline(memPipeline) 103 | 104 | // ! IO 105 | val io = new Bundle { 106 | val intrpt = in(Bits(8 bits)) default 0 107 | val debug = config.debug generate out(new DebugInterface()) 108 | val iBus = master(fetchPipeline.service(classOf[ICachePlugin]).iBus.toAxi4()) 109 | val dBus = master(memPipeline.service(classOf[DCachePlugin]).dBus) 110 | val udBus = master(memPipeline.service(classOf[UncachedAccessPlugin]).udBus) 111 | } 112 | 113 | io.debug.wb.pc := 0 114 | io.debug.wb.inst := 0 115 | io.debug.wb.rf.wen := 0 116 | io.debug.wb.rf.wnum := 0 117 | io.debug.wb.rf.wdata := 0 118 | 119 | // ! Global Plugins 120 | type T = MyCPUCore 121 | plugins ++= List( 122 | new PhysRegFilePlugin(config.regFile), 123 | new ROBFIFOPlugin(config), 124 | new CommitPlugin(config), 125 | new BypassNetworkPlugin(config.regFile), 126 | // Reservation stations 127 | new IntIssueQueuePlugin(config), 128 | new MulDivIssueQueuePlugin(config), 129 | new MemIssueQueuePlugin(config), 130 | // Memory Speculative Wakeup 131 | new SpeculativeWakeupHandler(), 132 | // Privilege 133 | new Timer64Plugin(), 134 | new ExceptionHandlerPlugin(), 135 | new InterruptHandlerPlugin(config), 136 | new CSRPlugin(), 137 | new MMUPlugin(config), 138 | new WaitHandlerPlugin() 139 | ).filter(_ != null) 140 | 141 | // ! Stages 142 | val IF1: Stage = fetchPipeline.IF1 143 | val IF2: Stage = fetchPipeline.IF2 144 | val ID: Stage = decodePipeline.ID 145 | val RENAME: Stage = decodePipeline.RENAME 146 | val DISPATCH: Stage = decodePipeline.DISPATCH 147 | val EXE: Stage = null 148 | val MEM1: Stage = memPipeline.MEM1 149 | val MEM2: Stage = memPipeline.MEM2 150 | val WB: Stage = null 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/pipeline/core/PhysRegFilePlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.core 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP._ 7 | import NOP.utils._ 8 | import NOP.builder._ 9 | import NOP.pipeline._ 10 | import NOP.pipeline.decode._ 11 | import NOP.pipeline.core._ 12 | 13 | import scala.collection.mutable 14 | 15 | // Write Port 16 | final case class RegWriteBundle(addrWidth: Int, dataWidth: Int = 32) extends Bundle { 17 | val addr = UInt(addrWidth bits) 18 | val data = Bits(dataWidth bits) 19 | } 20 | 21 | // Write Port with bypass 22 | case class PRFWritePort(hw: Flow[RegWriteBundle], bypass: Boolean) 23 | 24 | class PhysRegFilePlugin(config: RegFileConfig) extends Plugin[MyCPUCore] { 25 | // 物理寄存器要经历如下状态: 26 | // 1. 被写分配,此后的指令都应该读新值,但是新值尚未写回。 27 | // 2. 写完成,此后的读都可以直接读寄存器。 28 | // 3. 释放,此后不应该有读发生。 29 | val regs = Vec(RegInit(BWord(0)), config.nPhysRegs) 30 | 31 | // TODO: [NOP] delete this debug code 32 | val debug_regs = out(Vec(Bits(32 bits), config.nPhysRegs)) 33 | debug_regs := regs 34 | 35 | val busys = Vec(RegInit(False), config.nPhysRegs) 36 | 37 | private val writePorts = mutable.ArrayBuffer[PRFWritePort]() 38 | val clearBusys = mutable.ArrayBuffer[Flow[UInt]]() 39 | private val readPorts = mutable.ArrayBuffer[(UInt, Bits)]() 40 | private val readBusys = mutable.ArrayBuffer[(UInt, Bool)]() 41 | 42 | def writePort(bypass: Boolean) = { 43 | val port = Flow(RegWriteBundle(config.prfAddrWidth)) // .setCompositeName(this, "writePort", true) 44 | writePorts += PRFWritePort(port, bypass) 45 | when(port.valid)(regs(port.payload.addr - 1) := port.payload.data) 46 | port 47 | } 48 | def clearBusy = { 49 | val port = Flow(UInt(config.prfAddrWidth bits)) 50 | clearBusys += port 51 | when(port.valid)(busys(port.payload - 1) := False) 52 | port 53 | } 54 | def readPort(addr: UInt) = { 55 | val port = Mux(addr === 0, BWord(0), regs(addr - 1)) // .setCompositeName(this, "readPort", true) 56 | readPorts += (addr -> port) 57 | port 58 | } 59 | 60 | def readBusy(addr: UInt) = { 61 | val port = Mux(addr === 0, False, busys(addr - 1)) // .setCompositeName(this, "readBusy", true) 62 | readBusys += (addr -> port) 63 | port 64 | } 65 | override def build(pipeline: MyCPUCore): Unit = pipeline plug new Area { 66 | if (pipeline.decodePipeline.serviceExist(classOf[RenamePlugin])) 67 | pipeline.decodePipeline.service(classOf[RenamePlugin]).freeList.io.pop.foreach { popPort => 68 | // 分配出去的寄存器要标记为busy 69 | // 空闲寄存器的busy随便标,因此这么做似乎没有问题 70 | // 需要清busy的寄存器必然不在free list中,直到其生命周期结束 71 | when(popPort.fire)(busys(popPort.payload - 1) := True) 72 | } 73 | for (i <- 0 until config.nPhysRegs) { 74 | val writeOH = writePorts.map { p => p.hw.valid && p.hw.addr - 1 === i } 75 | when(writeOH.orR) { regs(i) := MuxOH(writeOH, writePorts.map(_.hw.payload.data)) } 76 | } 77 | 78 | // ! Bypassing Logic 79 | val bypassWritePorts = writePorts.filter(_.bypass) 80 | 81 | // Bypassing Logic for read ports 82 | readPorts.foreach { r => 83 | // p0永远不应该被写,它是r0的固定映射 84 | // 永远不应该出现写冲突,rename处理掉了写冲突 85 | val writeOH = bypassWritePorts.map { p => p.hw.valid && p.hw.payload.addr === r._1 } 86 | when(writeOH.orR) { r._2 := MuxOH(writeOH, bypassWritePorts.map(_.hw.payload.data)) } 87 | } 88 | 89 | // Bypassing Logic for readBusys 90 | readBusys.foreach { r => 91 | // busy需要被前传,这样write back就会唤醒正在进入IQ的指令 92 | val writeOH = clearBusys.map { p => p.valid && p.payload === r._1 } 93 | when(writeOH.orR) { r._2 := False } 94 | } 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/pipeline/core/ROBFIFOPlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.core 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import NOP._ 6 | import NOP.utils._ 7 | import NOP.builder._ 8 | import NOP.constants.`enum`.LoadStoreType 9 | import NOP.pipeline._ 10 | import NOP.pipeline.decode._ 11 | import NOP.pipeline.core._ 12 | 13 | import scala.collection.mutable.ArrayBuffer 14 | 15 | class ROBFIFOPlugin(config: MyCPUConfig) extends Plugin[MyCPUCore] { 16 | private val prfAddressWidth = config.regFile.prfAddrWidth 17 | private val robDepth = config.rob.robDepth 18 | private val retireWidth = config.rob.retireWidth 19 | private val decodeWidth = config.decode.decodeWidth 20 | private val addressWidth = config.rob.robAddressWidth 21 | private val entryType = HardType(ROBEntryBundle(config)) 22 | private val infoType = HardType(ROBEntryInfoBundle(config)) 23 | private val stateType = HardType(ROBEntryStateBundle(config.regFile)) 24 | require(isPow2(robDepth)) 25 | 26 | val robInfo = new MultiPortFIFOSyncImpl(infoType, robDepth, decodeWidth, retireWidth) { 27 | val flush = in(Bool) 28 | when(flush) { 29 | pushPtr := 0 30 | popPtr := 0 31 | isRisingOccupancy := False 32 | // 可pop不可push,否则数据会丢失 33 | // fifoIO.push.foreach(_.setBlocked()) 34 | } 35 | pushPtr.asOutput() 36 | popPtr.asOutput() 37 | popCount.asOutput() 38 | } 39 | 40 | // ROB state随机写口 41 | private val completePorts = ArrayBuffer[Flow[UInt]]() 42 | private val lsuPorts = ArrayBuffer[Flow[ROBStateLSUPortBundle]]() 43 | private val bruPorts = ArrayBuffer[Flow[ROBStateBRUPortBundle]]() 44 | private val aluPorts = ArrayBuffer[Flow[ROBStateALUPortBundle]]() 45 | def completePort: Flow[UInt] = { 46 | val port = Flow(UInt(config.rob.robAddressWidth bits)) 47 | completePorts += port 48 | port 49 | } 50 | def lsuPort = { 51 | val port = Flow(ROBStateLSUPortBundle(config)) 52 | lsuPorts += port 53 | port 54 | } 55 | def bruPort = { 56 | val port = Flow(ROBStateBRUPortBundle(config)) 57 | bruPorts += port 58 | port 59 | } 60 | def aluPort = { 61 | val port = Flow(ROBStateALUPortBundle(config)) 62 | aluPorts += port 63 | port 64 | } 65 | 66 | val robState = Reg(Vec(stateType, robDepth)) 67 | 68 | val pushPtr = robInfo.pushPtr 69 | val popPtr = robInfo.popPtr 70 | val fifoIO = new Bundle { 71 | 72 | /** push的valid,pop的ready,都要遵循连续性,从头开始的第一个0就表示了停止的位置,后面的1都会被忽略。 73 | */ 74 | val push = Vec(Stream(ROBEntryBundle(config, false)), decodeWidth) 75 | val pop = Vec(Stream(entryType), retireWidth) 76 | // 同步清空FIFO 77 | val flush = Bool 78 | } 79 | 80 | // TODO: [NOP] delete this debugging code 81 | val debug_fifoIO = out(new Bundle { 82 | val push = Vec(Stream(ROBEntryBundle(config, false)), decodeWidth) 83 | val pop = Vec(Stream(entryType), retireWidth) 84 | val flush = Bool() 85 | }) 86 | debug_fifoIO.assignAllByName(fifoIO) 87 | 88 | val debugPopWriteData = config.debug generate out(Vec(BWord(), retireWidth)) 89 | override def build(pipeline: MyCPUCore): Unit = pipeline plug new Area { 90 | if (config.debug) { 91 | val physRegs = pipeline.service(classOf[PhysRegFilePlugin]).regs 92 | for (i <- 0 until retireWidth) { 93 | debugPopWriteData(i) := physRegs(fifoIO.pop(i).payload.info.rename.wReg) // bypassing for debug :) 94 | } 95 | } 96 | // FIFO与MultiPortFIFOVec完全一致,但是ROB不止FIFO端口 97 | // multi-port FIFO io 98 | for (i <- 0 until retireWidth) 99 | fifoIO.pop(i).translateFrom(robInfo.io.pop(i)) { (entry, info) => 100 | entry.info := info 101 | entry.state.setAsReg().allowOverride 102 | entry.state := robState(popPtr + robInfo.popCount + i) 103 | } 104 | 105 | for (i <- 0 until decodeWidth) { 106 | robInfo.io.push(i).translateFrom(fifoIO.push(i)) { (info, entry) => 107 | info := entry.info 108 | } 109 | when(fifoIO.push(i).fire) { 110 | robState(pushPtr + i).assignSomeByName(fifoIO.push(i).payload.state) 111 | for (j <- i until retireWidth) when(pushPtr + i === popPtr + robInfo.popCount + j) { 112 | fifoIO.pop(j).payload.state.assignSomeByName(fifoIO.push(i).payload.state) 113 | } 114 | } 115 | } 116 | robInfo.flush := fifoIO.flush 117 | 118 | // random write ports 119 | println("ROB port summary:") 120 | printf(" pure ALU: %d\n", aluPorts.size) 121 | printf(" ALU&BRU: %d\n", bruPorts.size) 122 | printf(" LSU: %d\n", lsuPorts.size) 123 | printf(" MDU: %d\n", completePorts.size) 124 | val portCount = aluPorts.size + bruPorts.size + lsuPorts.size + completePorts.size 125 | printf(" issue width: %d\n", portCount) 126 | 127 | val defaultState = new ROBEntryStateBundle(config.regFile, true) 128 | 129 | // 完成则代表这条指令已经可以提交 130 | defaultState.complete := False 131 | // 完整异常信息 132 | defaultState.except.setIdle() 133 | // 分支预测恢复信息 134 | defaultState.mispredict := False 135 | defaultState.actualTaken := False 136 | 137 | // When full is true 138 | // LSU检测到uncached区段,需要提交时操作 139 | defaultState.lsuUncached := False 140 | // INT执行结果 141 | defaultState.intResult := 0 142 | 143 | // TODO: [NOP] DiffTest Bundle. Remove this in the future. 144 | defaultState.isCount := False 145 | defaultState.count64ReadValue := 0 146 | defaultState.csrRstat := False 147 | defaultState.csrRdata := 0 148 | defaultState.isLoad := False 149 | defaultState.isStore := False 150 | defaultState.isLL := False 151 | defaultState.isSC := False 152 | defaultState.lsType := LoadStoreType.WORD 153 | defaultState.vAddr := 0 154 | defaultState.pAddr := 0 155 | defaultState.storeData := 0 156 | defaultState.myPC := B(0x0eadbeef, 32 bits).asUInt 157 | 158 | for (p <- aluPorts) 159 | when(p.valid) { 160 | robState(p.robIdx) := defaultState 161 | robState(p.robIdx).allowOverride() 162 | robState(p.robIdx).complete := True 163 | robState(p.robIdx).assignSomeByName(p.payload) 164 | for (j <- 0 until retireWidth) when(p.robIdx === popPtr + robInfo.popCount + j) { 165 | fifoIO.pop(j).payload.state := defaultState 166 | fifoIO.pop(j).payload.state.allowOverride() 167 | fifoIO.pop(j).payload.state.complete := True 168 | fifoIO.pop(j).payload.state.assignSomeByName(p.payload) 169 | } 170 | } 171 | for (p <- bruPorts) 172 | when(p.valid) { 173 | robState(p.robIdx) := defaultState 174 | robState(p.robIdx).allowOverride() 175 | robState(p.robIdx).complete := True 176 | robState(p.robIdx).assignSomeByName(p.payload) 177 | for (j <- 0 until retireWidth) when(p.robIdx === popPtr + robInfo.popCount + j) { 178 | fifoIO.pop(j).payload.state := defaultState 179 | fifoIO.pop(j).payload.state.allowOverride() 180 | fifoIO.pop(j).payload.state.complete := True 181 | fifoIO.pop(j).payload.state.assignSomeByName(p.payload) 182 | } 183 | } 184 | for (p <- lsuPorts) 185 | when(p.valid) { 186 | robState(p.robIdx) := defaultState 187 | robState(p.robIdx).allowOverride() 188 | robState(p.robIdx).complete := True 189 | robState(p.robIdx).assignSomeByName(p.payload) 190 | for (j <- 0 until retireWidth) when(p.robIdx === popPtr + robInfo.popCount + j) { 191 | fifoIO.pop(j).payload.state := defaultState 192 | fifoIO.pop(j).payload.state.allowOverride() 193 | fifoIO.pop(j).payload.state.complete := True 194 | fifoIO.pop(j).payload.state.assignSomeByName(p.payload) 195 | } 196 | } 197 | for (p <- completePorts) 198 | when(p.valid) { 199 | robState(p.payload) := defaultState 200 | robState(p.payload).allowOverride() 201 | robState(p.payload).complete := True 202 | for (j <- 0 until retireWidth) when(p.payload === popPtr + robInfo.popCount + j) { 203 | fifoIO.pop(j).payload.state := defaultState 204 | fifoIO.pop(j).payload.state.allowOverride() 205 | fifoIO.pop(j).payload.state.complete := True 206 | } 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/pipeline/core/ReorderBufferModel.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.core 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import NOP._ 6 | import NOP.utils._ 7 | import NOP.builder._ 8 | import NOP.constants.`enum`.LoadStoreType 9 | import NOP.pipeline._ 10 | 11 | // Exception 12 | final case class ExceptionPayloadBundle(genBadVA: Boolean) extends Bundle { 13 | val code = Bits(6 bits) 14 | val subcode = Bits(9 bits) 15 | val badVA = genBadVA generate UWord() 16 | val isTLBRefill = Bool() 17 | } 18 | 19 | // RegFile 20 | final case class RegFileMappingEntryBundle(config: RegFileConfig) extends Bundle { 21 | val addr = UInt(config.arfAddrWidth bits) 22 | val prevAddr = UInt(config.prfAddrWidth bits) 23 | val prfAddr = UInt(config.prfAddrWidth bits) 24 | } 25 | 26 | /** ROB表项。 27 | * 28 | * ROB主要关心一条指令的执行状态,和需要提交给各个模块的提交信息。 29 | */ 30 | final case class ROBEntryBundle(config: MyCPUConfig, fullState: Boolean = true) extends Bundle { 31 | val info = ROBEntryInfoBundle(config) 32 | val state = ROBEntryStateBundle(config.regFile, fullState) 33 | } 34 | 35 | /** Info信息应当用于提交,不需要被writeback修改。 36 | * 37 | * @param config 38 | */ 39 | final case class ROBEntryInfoBundle(config: MyCPUConfig) extends Bundle { 40 | // ROB不需要完整指令信息 41 | val uop = ROBMicroOp(config) 42 | // 似乎只需要rename的写口?用于提交ARF状态 43 | val rename = ROBRenameRecordBundle(config.regFile) 44 | // frontend exception的badVA是pc 45 | val frontendExc = Bool 46 | } 47 | 48 | /** State信息关心指令当前执行状态,通常writeback会改变这些状态。 49 | * 50 | * @param config 51 | */ 52 | final case class ROBEntryStateBundle(config: RegFileConfig, full: Boolean = true) extends Bundle { 53 | // 完成则代表这条指令已经可以提交 54 | val complete = Bool 55 | // 完整异常信息 56 | val except = Flow(ExceptionPayloadBundle(full)) 57 | // 分支预测恢复信息 58 | val mispredict = Bool 59 | val actualTaken = Bool 60 | 61 | // When full is true 62 | // LSU检测到uncached区段,需要提交时操作 63 | val lsuUncached = full generate Bool() 64 | // INT执行结果 65 | val intResult = full generate UWord() 66 | 67 | // TODO: [NOP] DiffTest Bundle. Remove this in the future. 68 | val isCount = full generate Bool() 69 | val count64ReadValue = full generate UInt(64 bits) 70 | val csrRstat = full generate Bool() 71 | val csrRdata = full generate UWord() 72 | val isLoad = full generate Bool() 73 | val isStore = full generate Bool() 74 | val isLL = full generate Bool() 75 | val isSC = full generate Bool() 76 | val lsType = full generate LoadStoreType() 77 | val vAddr = full generate UWord() 78 | val pAddr = full generate UWord() 79 | val storeData = full generate UWord() 80 | val myPC = full generate UWord() 81 | } 82 | 83 | // Subset for ROBEntryState 84 | final case class ROBStateCompletePortBundle(config: MyCPUConfig) extends Bundle { 85 | val robIdx = UInt(config.rob.robAddressWidth bits) 86 | val complete = Bool 87 | } 88 | 89 | final case class ROBStateLSUPortBundle(config: MyCPUConfig) extends Bundle { 90 | val robIdx = UInt(config.rob.robAddressWidth bits) 91 | val except = Flow(ExceptionPayloadBundle(true)) 92 | val lsuUncached = Bool 93 | val intResult = UWord() 94 | // TODO: [NOP] DiffTest Bundle. Remove this in the future. 95 | val isLoad = Bool() 96 | val isStore = Bool() 97 | val isLL = Bool() 98 | val isSC = Bool() 99 | val lsType = LoadStoreType() 100 | val vAddr = UWord() 101 | val pAddr = UWord() 102 | val storeData = UWord() 103 | val myPC = UWord() 104 | } 105 | 106 | final case class ROBStateALUPortBundle(config: MyCPUConfig) extends Bundle { 107 | val robIdx = UInt(config.rob.robAddressWidth bits) 108 | val except = Flow(ExceptionPayloadBundle(false)) 109 | 110 | // TODO: [NOP] DiffTest Bundle. Remove this in the future. 111 | val intResult = UWord() 112 | val isCount = Bool() 113 | val count64ReadValue = UInt(64 bits) 114 | val csrRstat = Bool() 115 | val csrRdata = UWord() 116 | val myPC = UWord() 117 | } 118 | 119 | final case class ROBStateBRUPortBundle(config: MyCPUConfig) extends Bundle { 120 | val robIdx = UInt(config.rob.robAddressWidth bits) 121 | val except = Flow(ExceptionPayloadBundle(false)) 122 | val mispredict = Bool 123 | val intResult = UWord() 124 | val actualTaken = Bool 125 | } 126 | -------------------------------------------------------------------------------- /src/pipeline/core/SpeculativeWakeupHandler.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.core 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP.builder._ 7 | 8 | class SpeculativeWakeupHandler extends Plugin[MyCPUCore] { 9 | val wakeupFailed = False 10 | val regWakeupFailed = RegNext(wakeupFailed, init = False) 11 | override def build(pipeline: MyCPUCore): Unit = {} 12 | } 13 | -------------------------------------------------------------------------------- /src/pipeline/core/Timer64Plugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.core 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import NOP.builder._ 6 | 7 | import scala.collection.mutable 8 | import NOP._ 9 | import NOP.utils._ 10 | import NOP.constants.enum._ 11 | import NOP.pipeline.core._ 12 | import NOP.pipeline.priviledge.InterruptHandlerPlugin 13 | 14 | class Timer64Plugin extends Plugin[MyCPUCore] { 15 | 16 | val timer64 = RegInit(U(0x0, 64 bits)) 17 | 18 | val highBits = out UInt (32 bits) 19 | val lowBits = out UInt (32 bits) 20 | val counterId = out UInt (32 bits) 21 | 22 | override def build(pipeline: MyCPUCore): Unit = { 23 | 24 | val InterruptHandlerPlugin = pipeline.service(classOf[InterruptHandlerPlugin]) 25 | 26 | counterId := InterruptHandlerPlugin.TID_TID 27 | highBits := timer64(63 downto 32) 28 | lowBits := timer64(31 downto 0) 29 | timer64 := timer64 + 1 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/pipeline/decode/DecodeMicroOP.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import NOP.utils._ 6 | import NOP.pipeline.fetch._ 7 | import NOP.pipeline.core._ 8 | import NOP.builder._ 9 | import NOP._ 10 | import NOP.constants.enum._ 11 | 12 | object MicroOpSignals { 13 | // 用同样的名字构造stageable,这样便于decoder编写 14 | // 与 micro op 保持一样的顺序,便于对照 15 | 16 | // dispatch 信息 17 | object fuType extends Stageable(FUType()) 18 | 19 | // REG r/w 信息 20 | object useRj extends Stageable(Bool) 21 | object useRk extends Stageable(Bool) 22 | object useRd extends Stageable(Bool) 23 | object isRegWrite extends Stageable(Bool) // doRegWrite = isRegWrite && Rd != 0 24 | object overrideRdToRA extends Stageable(Bool) 25 | object overrideRdToRj extends Stageable(Bool) 26 | 27 | // IMM 28 | object immExtendType extends Stageable(ExtendType()) 29 | 30 | // ALU 31 | object aluOp extends Stageable(ALUOpType()) 32 | 33 | // CSR 34 | object readCSR extends Stageable(Bool()) 35 | 36 | // Timer64 37 | object readTimer64H extends Stageable(Bool()) 38 | 39 | // LSU 40 | object lsType extends Stageable(LoadStoreType()) 41 | object isLoad extends Stageable(Bool) 42 | object isStore extends Stageable(Bool) 43 | 44 | // BRU 45 | object cmpOp extends Stageable(CompareOpType()) 46 | object isBranch extends Stageable(Bool) 47 | object isJump extends Stageable(Bool) 48 | object isJR extends Stageable(Bool) 49 | 50 | // MUL / DIV 51 | object signed extends Stageable(Bool) 52 | 53 | // Priviledge-related OP 54 | object isErtn extends Stageable(Bool()) 55 | object isSyscall extends Stageable(Bool()) // In epilogue, assigned directly to exception.valid = True 56 | object isBreak extends Stageable(Bool()) // In epilogue, assigned directly to exception.valid = True 57 | object isBar extends Stageable(Bool()) 58 | 59 | object tlbOp extends Stageable(TLBOpType()) 60 | object operateCache extends Stageable(Bool()) 61 | object isWait extends Stageable(Bool()) 62 | object isLL extends Stageable(Bool()) 63 | object isSC extends Stageable(Bool()) 64 | 65 | } 66 | 67 | final case class MicroOp(config: MyCPUConfig) extends Bundle { 68 | val pc = UWord() // Assigned in Epilogue 69 | val inst = BWord() // Assigned in Epilogue 70 | 71 | // Branch prediction 72 | val predInfo = BranchPredictInfoBundle() // Assigned in Epilogue 73 | val predRecover = PredictRecoverBundle(config.frontend) // Assigned in Epilogue 74 | 75 | // dispatch信息 76 | val fuType = FUType() // Assigned in Main 77 | 78 | // REG r/w信息 79 | val useRj = Bool 80 | val useRk = Bool 81 | val useRd = Bool 82 | val wbAddr = UInt(config.regFile.arfAddrWidth bits) // Assigned in Epilogue 83 | val doRegWrite = Bool // Re-assigned in Epilogue 84 | 85 | // IMM 86 | val immExtendType = ExtendType() 87 | 88 | // ALU 89 | val aluOp = ALUOpType() 90 | 91 | // LSU 92 | val lsType = LoadStoreType() 93 | val isLoad = Bool 94 | val isStore = Bool 95 | 96 | // BRU 97 | val cmpOp = CompareOpType() 98 | val isBranch = Bool 99 | val isJump = Bool 100 | val isJR = Bool 101 | val branchLike = Bool // Assigned in Epilogue 102 | 103 | // CSR 104 | val writeCSR = Bool() // Assigned in Epilogue 105 | val readCSR = Bool() 106 | 107 | // Timer64 108 | val readTimer64L = Bool() 109 | val readTimer64H = Bool() 110 | val readTimer64ID = Bool() 111 | 112 | // TLB操作 113 | val tlbOp = TLBOpType() 114 | def operateTLB = tlbOp =/= TLBOpType.NONE 115 | 116 | // cache操作 117 | val operateCache = Bool 118 | 119 | // // WAIT指令 120 | val isWait = Bool 121 | val isLL = Bool() 122 | val isSC = Bool() 123 | 124 | // commit信息 125 | val uniqueRetire = Bool // Assigned in Epilogue 126 | 127 | // MUL / DIV 128 | val signed = Bool 129 | 130 | // Exception 131 | val except = Flow(ExceptionPayloadBundle(false)) // Assigned in Epilogue 132 | val isErtn = Bool // Assigned in Epilogue 133 | 134 | // control flow transfer 135 | val flushState = Bool // Assigned in Epilogue 136 | 137 | def needNotExecute = except.valid || fuType === FUType.NONE 138 | } 139 | 140 | final case class IntIQMicroOp() extends Bundle { 141 | // Meta information 142 | val pc = UInt(32 bits) 143 | val inst = Bits(32 bits) 144 | 145 | // Branch prediction 146 | val predInfo = BranchPredictInfoBundle() 147 | 148 | // dispatch信息 149 | val fuType = FUType() 150 | 151 | // REG r/w信息 152 | val useRj = Bool 153 | val useRk = Bool 154 | val useRd = Bool 155 | val doRegWrite = Bool 156 | 157 | // IMM 158 | val immExtendType = ExtendType() 159 | 160 | // ALU 161 | val aluOp = ALUOpType() 162 | 163 | // BRU信息 164 | val cmpOp = CompareOpType() 165 | val isBranch = Bool() 166 | val isJump = Bool() 167 | val isJR = Bool() 168 | def branchLike = isBranch || isJump || isJR 169 | 170 | // CSR 171 | val writeCSR = Bool() 172 | val readCSR = Bool() 173 | 174 | // Timer64 175 | val readTimer64L = Bool() 176 | val readTimer64H = Bool() 177 | val readTimer64ID = Bool() 178 | 179 | // TLB操作 180 | val tlbOp = TLBOpType() 181 | def operateTLB = tlbOp =/= TLBOpType.NONE 182 | } 183 | 184 | final case class MulDivIQMicroOp() extends Bundle { 185 | // dispatch信息 186 | val fuType = FUType() 187 | // REG r/w信息 188 | val doRegWrite = Bool() 189 | // If MULH, DIV or MOD is signed 190 | val signed = Bool() 191 | } 192 | 193 | final case class MemIQMicroOp() extends Bundle { 194 | // Meta information 195 | val pc = UInt(32 bits) 196 | val inst = Bits(32 bits) 197 | 198 | val doRegWrite = Bool() 199 | // LSU信息 200 | val lsType = LoadStoreType() 201 | val isLoad = Bool() 202 | val isStore = Bool() 203 | 204 | val isLL = Bool() 205 | val isSC = Bool() 206 | 207 | // 需要单独赋值 208 | val immField = Bits(32 bits) 209 | val cacheOp = CacheOpType() // CACOP or PRELD Cache OP 210 | val cacheSel = CacheSelType() 211 | } 212 | 213 | final case class ROBMicroOp(config: MyCPUConfig) extends Bundle { 214 | val pc = UWord() 215 | val inst = BWord() 216 | // Branch Prediction 217 | val predInfo = BranchPredictInfoBundle(false) 218 | val predRecover = PredictRecoverBundle(config.frontend) 219 | // Write registers 220 | val wbAddr = UInt(config.regFile.arfAddrWidth bits) 221 | val doRegWrite = Bool 222 | 223 | // IMM、ALU、LSU、BRU信息均与执行相关,仅部分保留 224 | 225 | // LSU信息 226 | val isLoad = Bool 227 | val isStore = Bool 228 | val lsType = LoadStoreType() 229 | 230 | // Timer64 231 | val readTimer64L = Bool() 232 | val readTimer64H = Bool() 233 | val readTimer64ID = Bool() 234 | 235 | // BRU信息 236 | val isBranch = Bool 237 | val isJump = Bool 238 | val isJR = Bool 239 | val branchLike = Bool 240 | 241 | // TLB操作 242 | val tlbOp = TLBOpType() 243 | def operateTLB = tlbOp =/= TLBOpType.NONE 244 | 245 | // cache操作 246 | val operateCache = Bool() 247 | 248 | // CSR 249 | val writeCSR = Bool() // Assigned in Epilogue 250 | val readCSR = Bool() 251 | 252 | // // WAIT指令 253 | val isWait = Bool() 254 | 255 | val isLL = Bool() 256 | val isSC = Bool() 257 | 258 | // commit信息 259 | val uniqueRetire = Bool() 260 | // // 异常被另行保存 261 | val isErtn = Bool() 262 | // 非branch的控制流转移 263 | val flushState = Bool() 264 | } 265 | -------------------------------------------------------------------------------- /src/pipeline/decode/DecodePipeline.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline 2 | 3 | import NOP.builder._ 4 | import NOP.pipeline._ 5 | import NOP._ 6 | 7 | import spinal.core._ 8 | import spinal.lib._ 9 | 10 | class DecodeSignals(config: MyCPUConfig) { 11 | private val decodeWidth = config.decode.decodeWidth 12 | object DECODE_PACKET extends Stageable(Vec(Flow(MicroOp(config)), decodeWidth)) 13 | object RENAME_RECORDS extends Stageable(Vec(RenameRecordBundle(config.regFile), decodeWidth)) 14 | object ROB_INDEXES extends Stageable(Vec(UInt(config.rob.robAddressWidth bits), decodeWidth)) 15 | } 16 | 17 | trait DecodePipeline extends Pipeline { 18 | type T = DecodePipeline 19 | val ID: Stage = null 20 | val RENAME: Stage = null 21 | val DISPATCH: Stage = null 22 | val signals: DecodeSignals 23 | } 24 | -------------------------------------------------------------------------------- /src/pipeline/decode/DecodeSignals.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import NOP.utils._ 6 | import NOP._ 7 | 8 | final case class RenameRecordBundle(config: RegFileConfig) extends Bundle { 9 | val rRegs = Vec(UInt(config.prfAddrWidth bits), config.rPortsEachInst) 10 | val wReg = UInt(config.prfAddrWidth bits) 11 | val wPrevReg = UInt(config.prfAddrWidth bits) 12 | } 13 | 14 | final case class ROBRenameRecordBundle(config: RegFileConfig) extends Bundle { 15 | val wReg = UInt(config.prfAddrWidth bits) 16 | val wPrevReg = UInt(config.prfAddrWidth bits) 17 | } 18 | 19 | // And MicroOps. See DecodeMicroOP.scala 20 | -------------------------------------------------------------------------------- /src/pipeline/decode/InstructionParser.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.decode 2 | 3 | import spinal.core._ 4 | import NOP.constants.enum._ 5 | 6 | case class InstructionParser(val inst: Bits) extends Bundle { 7 | 8 | def rd = inst(4 downto 0).asUInt 9 | def rj = inst(9 downto 5).asUInt 10 | def rk = inst(14 downto 10).asUInt 11 | 12 | def sa = inst(14 downto 10).asUInt 13 | 14 | def imm12 = inst(21 downto 10).asUInt 15 | def signExtendImm12 = imm12.asSInt.resize(32 bits).asUInt 16 | def zeroExtendImm12 = imm12.resize(32 bits) 17 | 18 | def imm20 = inst(24 downto 5).asUInt 19 | def signExtendImm20_2 = (imm20 @@ U(0, 2 bits)).asSInt.resize(32 bits).asUInt 20 | def signExtendImm20_12 = imm20 @@ U(0, 12 bits) 21 | 22 | def csrAddr = inst(23 downto 10).asUInt 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/pipeline/decode/RenameModel.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.decode 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP._ 7 | import NOP.builder._ 8 | import NOP.pipeline._ 9 | 10 | final case class RegRenameBundle(arfAddrWidth: Int, prfAddrWidth: Int) extends Bundle with IMasterSlave { 11 | val req = Flow(UInt(arfAddrWidth bits)) 12 | val rsp = UInt(prfAddrWidth bits) 13 | override def asMaster(): Unit = { 14 | master(req) 15 | in(rsp) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/pipeline/decode/RenamePlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.decode 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP._ 7 | import NOP.builder._ 8 | import NOP.utils._ 9 | import NOP.pipeline._ 10 | import NOP.pipeline.core._ 11 | 12 | class RenamePlugin(config: MyCPUConfig) extends Plugin[DecodePipeline] { 13 | private val rfConfig = config.regFile 14 | private val decodeWidth = config.decode.decodeWidth 15 | private val retireWidth = config.rob.retireWidth 16 | private val arfAddrWidth = rfConfig.arfAddrWidth 17 | private val prfAddrWidth = rfConfig.prfAddrWidth 18 | private val prfAddrType = HardType(UInt(prfAddrWidth bits)) 19 | // speculative register alias table 20 | val sRAT = Vec.tabulate(rfConfig.nArchRegs - 1) { i => RegInit(U(i + 1, prfAddrWidth bits)) } 21 | 22 | // architecture register alias table 23 | val aRAT = Vec.tabulate(rfConfig.nArchRegs - 1) { i => RegInit(U(i + 1, prfAddrWidth bits)) } 24 | // freeList永远不可能超过满,因为ARF必然占用其中的位置 25 | val freeList = new MultiPortFIFOVecOutReg( 26 | prfAddrType, 27 | rfConfig.nPhysRegs + 1 - rfConfig.nArchRegs, 28 | retireWidth, 29 | decodeWidth, 30 | dataInit = { i => U(i + rfConfig.nArchRegs, prfAddrWidth bits) }, 31 | initFull = true 32 | ) { 33 | val recover = in(Bool) 34 | when(recover) { 35 | // 非满情况下popPtr->pushPtr是有效数据,将其恢复为满,相当于所有最新pop的都回滚了。 36 | // 而这些pop的正是rename占用的speculative registers,数量也正好等于没有retire的写数量。 37 | // 注意到每一个写都会导致rename的时候pop一个新reg作为speculative,retire的时候push旧的, 38 | // 旧的从architecture变成free,新的从speculative变成architecture。 39 | // Remark. 应当观察到如下事情:每一条写指令pop free list和push free list的ptr值是一样的。 40 | // 重要!以下的修改fix掉了recover之后一个周期的pop payload。但要求recover周期不能动push和pop。 41 | // popPtr := 0 42 | // pushPtr := 0 43 | pushPtr := popPtr 44 | isRisingOccupancy := True 45 | } 46 | } 47 | 48 | override def build(pipeline: DecodePipeline): Unit = pipeline.RENAME plug new Area { 49 | import pipeline.RENAME._ 50 | 51 | // TODO: [NOP] remove this debugging code 52 | val debug_sRAT = out(Bits(prfAddrWidth * (rfConfig.nArchRegs - 1) bits)) 53 | val debug_aRAT = out(Bits(prfAddrWidth * (rfConfig.nArchRegs - 1) bits)) 54 | debug_sRAT := sRAT.asBits 55 | debug_aRAT := aRAT.asBits 56 | pipeline.update_signal("sRAT", debug_sRAT) 57 | pipeline.update_signal("aRAT", debug_aRAT) 58 | 59 | val decPacket = input(pipeline.signals.DECODE_PACKET) 60 | assert(rfConfig.rPortsEachInst == 2) 61 | def portMatch(dest: RegRenameBundle, src: RegRenameBundle) = 62 | src.req.valid && src.req.payload === dest.req.payload 63 | val regReads = 64 | Vec( 65 | Vec(RegRenameBundle(arfAddrWidth, prfAddrWidth), rfConfig.rPortsEachInst + 1), 66 | decodeWidth 67 | ) 68 | val regWrites = 69 | Vec(RegRenameBundle(arfAddrWidth, prfAddrWidth), decodeWidth) 70 | val noFreeRegs = False 71 | freeList.io.pop.foreach(_.setBlocked()) 72 | for (i <- 0 until decPacket.size; valid = decPacket(i).valid; uop = decPacket(i).payload) { 73 | val fields = InstructionParser(uop.inst) 74 | regReads(i)(0).req.valid := uop.useRj && valid 75 | regReads(i)(0).req.payload := fields.rj 76 | regReads(i)(1).req.valid := (uop.useRk || uop.useRd) && valid 77 | regReads(i)(1).req.payload := Mux(uop.useRk, fields.rk, fields.rd) 78 | val wPort = regWrites(i) 79 | wPort.req.valid := uop.doRegWrite && valid 80 | wPort.req.payload := uop.wbAddr 81 | // 将写地址添加到读,读出来的就是此逻辑位置上旧的映射 82 | regReads(i)(2).req := wPort.req 83 | regReads(i).foreach { rPort => 84 | // 无相关性,默认读RAT 85 | when(rPort.req.payload === 0) { 86 | rPort.rsp := 0 87 | } otherwise { 88 | rPort.rsp := sRAT(rPort.req.payload - 1) 89 | } 90 | // 前面的写match本次读,RAW相关性,读最新值 91 | i > 0 generate regWrites.take(i).foreach { p => 92 | when(portMatch(rPort, p)) { 93 | rPort.rsp := p.rsp 94 | } 95 | } 96 | } 97 | // pop出一个空闲寄存器 98 | val popPort = freeList.io.pop.dataType().setCompositeName(wPort, "popPort") 99 | popPort.setIdle() 100 | if (i > 0) { 101 | // 互联pop口,保证pop连续性 102 | val popIdx = 103 | CountOne(regWrites.take(i).map(_.req.valid)).setCompositeName(wPort, "popIdx") 104 | for (j <- 0 to i) 105 | when(popIdx === j && regWrites(i).req.valid)(popPort <> freeList.io.pop(j)) 106 | } else { 107 | when(regWrites(i).req.valid)(popPort <> freeList.io.pop(0)) 108 | } 109 | popPort.ready := arbitration.isValidNotStuck && wPort.req.valid 110 | // freeList空了 111 | noFreeRegs setWhen (arbitration.isValid && wPort.req.valid && !popPort.valid) 112 | wPort.rsp := popPort.payload 113 | // 后覆盖前,解决了WAW相关性 114 | when(arbitration.isValidNotStuck && wPort.req.valid)(sRAT(wPort.req.payload - 1) := wPort.rsp) 115 | // RENAME结果插入流水线(给ROB用) 116 | import pipeline.signals.RENAME_RECORDS 117 | for (j <- 0 until rfConfig.rPortsEachInst) { 118 | insert(RENAME_RECORDS)(i).rRegs(j) := regReads(i)(j).rsp 119 | } 120 | insert(RENAME_RECORDS)(i).wPrevReg := regReads(i)(2).rsp 121 | insert(RENAME_RECORDS)(i).wReg := regWrites(i).rsp 122 | } 123 | arbitration.haltItself setWhen noFreeRegs 124 | 125 | val arfCommit = pipeline.globalService(classOf[CommitPlugin]) 126 | // 提交时,修改aRAT并释放sRAT 127 | freeList.io.push.foreach(_.setIdle()) 128 | for (i <- 0 until retireWidth; commit = arfCommit.arfCommits(i)) { 129 | val port = freeList.io.push.dataType().setCompositeName(commit, "pushPort").setBlocked() 130 | if (i > 0) { 131 | // 互联push口,保证push连续性 132 | val pushIdx = 133 | CountOne(arfCommit.arfCommits.take(i).map(_.valid)).setCompositeName(commit, "pushIdx") 134 | for (j <- 0 to i) 135 | when(pushIdx === j && commit.valid)(port <> freeList.io.push(j)) 136 | } else { 137 | when(commit.valid)(port <> freeList.io.push(0)) 138 | } 139 | port.valid := commit.valid 140 | port.payload := commit.payload.prevAddr 141 | when(commit.valid) { 142 | aRAT(commit.payload.addr - 1) := commit.payload.prfAddr 143 | } 144 | } 145 | freeList.io.push.drop(retireWidth).foreach(_.setIdle()) 146 | // 分支预测恢复时,将aRAT拷贝进sRAT 147 | when(arfCommit.recoverPRF) { 148 | sRAT := aRAT 149 | } 150 | // 预测恢复时freeList也要恢复 151 | freeList.recover := arfCommit.recoverPRF 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/pipeline/exe/ALU.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.exe 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP._ 7 | import NOP.builder._ 8 | import NOP.constants.enum._ 9 | import NOP.pipeline._ 10 | import NOP.utils._ 11 | 12 | // Combinatorial ALU 13 | class ALU() extends Component { 14 | val io = new Bundle { 15 | val src1 = in(UWord()) 16 | val src2 = in(UWord()) 17 | val sa = in(UInt(5 bits)) 18 | val op = in(ALUOpType()) 19 | val result = out(UWord()) 20 | } 21 | import io._ 22 | 23 | switch(op) { 24 | import ALUOpType._ 25 | is(ADD, LU12I, PCADDI, PCADDU12I) { 26 | result := src1 + src2 27 | } 28 | is(ADDU) { 29 | result := src1 + src2 30 | } 31 | is(SUB) { 32 | result := src1 - src2 33 | } 34 | is(SUBU) { 35 | result := src1 - src2 36 | } 37 | is(AND) { 38 | result := src1 & src2 39 | } 40 | is(OR) { 41 | result := src1 | src2 42 | } 43 | is(XOR) { 44 | result := src1 ^ src2 45 | } 46 | is(NOR) { 47 | result := ~(src1 | src2) 48 | } 49 | is(SLT) { 50 | result := (src1.asSInt < src2.asSInt).asUInt.resized 51 | } 52 | is(SLTU) { 53 | result := (src1 < src2).asUInt.resized 54 | } 55 | is(SLL) { 56 | result := src1 |<< sa 57 | } 58 | is(SRL) { 59 | result := src1 |>> sa 60 | } 61 | is(SRA) { 62 | result := (src1.asSInt >> sa).asUInt 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/pipeline/exe/BRU.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.exe 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP._ 7 | import NOP.builder._ 8 | import NOP.constants.enum._ 9 | import NOP.pipeline._ 10 | import NOP.utils._ 11 | 12 | class BRU extends Component { 13 | val io = new Bundle { 14 | // predicts 15 | val predictJump = in(Bool) 16 | val predictAddr = in(UWord()) 17 | 18 | // actual 19 | val isBranch = in(Bool) 20 | val isJump = in(Bool) // B, BL 21 | val isJR = in(Bool) // JIRL 22 | 23 | val pc = in(UWord()) 24 | val rj = in(UInt()) 25 | val inst = in(Bits(32 bits)) 26 | val condition = in(Bool) 27 | 28 | // outputs 29 | val mispredict = out(Bool) 30 | val actualTarget = out(UWord()) // without prediction, actual target 31 | } 32 | 33 | import io._ 34 | val nextPC = pc + 4 35 | 36 | // BEQ, BNE, BLT, BGE, BLTU, BGEU 37 | val branchOffset = UWord() 38 | branchOffset := (inst(25 downto 10).asSInt @@ U(0, 2 bits)).resize(32 bits).asUInt 39 | 40 | // B or BL 41 | val jumpTarget = UWord() 42 | jumpTarget := pc + (inst(9 downto 0).asSInt @@ inst(25 downto 10).asSInt @@ U(0, 2 bits)).resize(32 bits).asUInt 43 | 44 | // JIRL 45 | val linkTarget = UWord() 46 | linkTarget := rj + (inst(25 downto 10).asUInt @@ U(0, 2 bits)).asSInt.resize(32 bits).asUInt 47 | 48 | when(isBranch) { 49 | actualTarget := pc + branchOffset 50 | } elsewhen (isJump) { 51 | actualTarget := jumpTarget 52 | } otherwise { 53 | actualTarget := linkTarget 54 | } 55 | 56 | when(isBranch || isJR || isJump) { 57 | mispredict := (predictJump ^ (condition || isJR || isJump)) || ((condition || isJR || isJump) && (actualTarget =/= predictAddr)) 58 | } otherwise { 59 | mispredict := predictJump 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/pipeline/exe/Comparator.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.exe 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP._ 7 | import NOP.builder._ 8 | import NOP.constants.enum._ 9 | import NOP.pipeline._ 10 | import NOP.utils._ 11 | import scala.collection.mutable.ArrayBuffer 12 | 13 | class Comparator() extends Component { 14 | val io = new Bundle { 15 | val src1 = in(UWord()) 16 | val src2 = in(UWord()) 17 | val op = in(CompareOpType()) 18 | val result = out(Bool) 19 | } 20 | 21 | import io._ 22 | import CompareOpType._ 23 | 24 | val eq = src1 === src2 25 | 26 | val lt = src1.asSInt < src2.asSInt 27 | val le = lt || eq 28 | val gt = !le 29 | val ge = !lt 30 | 31 | val ltu = src1 < src2 32 | val leu = ltu || eq 33 | val gtu = !leu 34 | val geu = !ltu 35 | 36 | val ltz = src1.msb 37 | val eqz = !src1.orR 38 | val lez = ltz || eqz 39 | val gtz = !lez 40 | val gez = !ltz 41 | 42 | val resultList = ArrayBuffer[(Any, Bool)]( 43 | EQ -> eq, 44 | NE -> !eq, 45 | EQZ -> eqz, 46 | NEZ -> !eqz, 47 | GE -> ge, 48 | LT -> lt, 49 | LE -> le, 50 | GT -> gt, 51 | GEU -> geu, 52 | LTU -> ltu, 53 | LEU -> leu, 54 | GTU -> gtu, 55 | GEZ -> !ltz, 56 | GTZ -> !lez, 57 | LEZ -> lez, 58 | LTZ -> ltz 59 | ) 60 | result := op.muxList(resultList) 61 | } 62 | -------------------------------------------------------------------------------- /src/pipeline/exe/ExecutePipeline.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline 2 | 3 | import NOP.builder._ 4 | import NOP.pipeline._ 5 | import NOP._ 6 | 7 | import spinal.core._ 8 | import spinal.lib._ 9 | 10 | trait ExecutePipeline extends Pipeline { 11 | type T = ExecutePipeline 12 | val ISS: Stage = null // issue/select 13 | val RRD: Stage = null // register read 14 | val EXE: Stage = null 15 | val WB: Stage = null 16 | } 17 | -------------------------------------------------------------------------------- /src/pipeline/exe/IntIssueQueuePlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.exe 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP._ 7 | import NOP.builder._ 8 | import NOP.utils._ 9 | import NOP.pipeline._ 10 | import NOP.pipeline.core._ 11 | import NOP.constants.enum._ 12 | 13 | /** 整数issue queue,不包括乘除法。采用压缩方法。 14 | */ 15 | class IntIssueQueuePlugin(config: MyCPUConfig) 16 | extends CompressedQueue( 17 | config.intIssue, 18 | config.decode.decodeWidth, 19 | HardType(IntIssueSlot(config)) 20 | ) { 21 | private val issConfig = config.intIssue 22 | val rPorts = config.regFile.rPortsEachInst 23 | val busyAddrs = Vec(UInt(config.regFile.prfAddrWidth bits), decodeWidth * rPorts) 24 | def fuMatch(uop: MicroOp): Bool = { 25 | uop.fuType === FUType.ALU || uop.fuType === FUType.CMP || 26 | uop.fuType === FUType.CSR || uop.fuType === FUType.TIMER || uop.fuType === FUType.INVTLB 27 | } 28 | 29 | // decode index 30 | object PUSH_INDEXES extends Stageable(Vec(Flow(UInt(log2Up(decodeWidth) bits)), decodeWidth)) 31 | 32 | override def build(pipeline: MyCPUCore): Unit = { 33 | // RENAME 34 | pipeline.RENAME plug new Area { 35 | import pipeline.RENAME._ 36 | val decPacket = input(pipeline.decodePipeline.signals.DECODE_PACKET) 37 | val enqueueMask = Bits(decodeWidth bits) 38 | val pushIndexes = insert(PUSH_INDEXES) 39 | for (i <- 0 until decodeWidth) { 40 | val valid = decPacket(i).valid 41 | val uop = decPacket(i).payload 42 | enqueueMask(i) := valid && fuMatch(uop) && !uop.except.valid // say, 110100 43 | // 在rename阶段计算互联 44 | pushIndexes(i).setIdle() 45 | if (i > 0) { 46 | val pushIdx = CountOne(enqueueMask.take(i)) // 1, 2, 2, 3, 3, 3 for i = 0, 1, 2, 3, 4, 5 47 | for (j <- 0 to i) when(pushIdx === j && enqueueMask(i))(pushIndexes(j).push(i)) // pushIndexes[pushIdx] = i 48 | } else { 49 | when(enqueueMask(i))(pushIndexes(0).push(i)) 50 | } 51 | } 52 | } 53 | 54 | // DISPATCH 55 | pipeline.DISPATCH plug new Area { 56 | genIssueSelect() 57 | genGlobalWakeup(pipeline.service(classOf[PhysRegFilePlugin]), rPorts) 58 | import pipeline.DISPATCH._ 59 | // 唤醒逻辑: 60 | // 1. 入队唤醒(dispatch入口读busy) 61 | // 2. 远程唤醒(监听写busy广播) 62 | // 3. 本地唤醒(监听select结果) 63 | // 入队 64 | val decPacket = input(pipeline.decodePipeline.signals.DECODE_PACKET) 65 | val renameRecs = input(pipeline.decodePipeline.signals.RENAME_RECORDS) 66 | val robIdxs = input(pipeline.decodePipeline.signals.ROB_INDEXES) 67 | val pushIndexes = input(PUSH_INDEXES) 68 | val pushPorts = Vec(slotType, decodeWidth) 69 | for (i <- 0 until decodeWidth) { 70 | // slot处理 71 | val valid = decPacket(i).valid 72 | val uop = decPacket(i).payload 73 | val rename = renameRecs(i) 74 | val slot = pushPorts(i) 75 | slot.uop.assignSomeByName(uop) 76 | 77 | for (j <- 0 until rPorts) { 78 | slot.rRegs(j).payload := rename.rRegs(j) 79 | busyAddrs(i * rPorts + j) := rename.rRegs(j) 80 | } 81 | // 入队唤醒(dispatch入口读busy) 82 | slot.rRegs(0).valid := !uop.useRj || !busyRsps(i * rPorts) 83 | slot.rRegs(1).valid := !(uop.useRk || uop.useRd) || !busyRsps(i * rPorts + 1) 84 | slot.wReg := rename.wReg 85 | slot.robIdx := robIdxs(i) 86 | 87 | // port互联,与rename相似 88 | val idx = pushIndexes(i) 89 | val port = queueIO.pushPorts(i) 90 | port.valid := arbitration.isValidNotStuck && idx.valid 91 | port.payload := pushPorts(idx.payload) 92 | arbitration.haltItself setWhen (arbitration.isValid && idx.valid && !port.ready) 93 | } 94 | 95 | // flush最高优先级 96 | val flush = pipeline.globalService(classOf[CommitFlush]).regFlush 97 | queueFlush setWhen flush 98 | } 99 | 100 | Component.current.afterElaboration { 101 | genEnqueueLogic() 102 | genCompressLogic() 103 | genFlushLogic() 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/pipeline/exe/IssueSlot.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP._ 7 | import NOP.utils._ 8 | import NOP.builder._ 9 | import NOP.pipeline._ 10 | import NOP.constants.enum._ 11 | 12 | class IssueSlot(config: MyCPUConfig, rPorts: Int) extends Bundle { 13 | protected val rfConfig = config.regFile 14 | // 套一个flow接wake up信号 15 | val rRegs = Vec(Flow(UInt(rfConfig.prfAddrWidth bits)), rPorts) 16 | // 用于寻址对应的rob表项,写相关信息 17 | val robIdx = UInt(config.rob.robAddressWidth bits) 18 | } 19 | 20 | final case class IntIssueSlot(config: MyCPUConfig) extends IssueSlot(config, 2) { 21 | // 这里也不需要完整的uop,但是和ROB需要的uop又不一样 22 | val uop = IntIQMicroOp() 23 | val wReg = UInt(rfConfig.prfAddrWidth bits) 24 | } 25 | 26 | final case class MulDivIssueSlot(config: MyCPUConfig) extends IssueSlot(config, 2) { 27 | val uop = MulDivIQMicroOp() 28 | val wReg = UInt(rfConfig.prfAddrWidth bits) 29 | } 30 | 31 | final case class MemIssueSlot(config: MyCPUConfig) extends IssueSlot(config, 2) { 32 | // 这里也不需要完整的uop,但是和ROB需要的uop又不一样 33 | val uop = MemIQMicroOp() 34 | val wReg = UInt(rfConfig.prfAddrWidth bits) 35 | } 36 | -------------------------------------------------------------------------------- /src/pipeline/exe/MulDivExecutePlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.exe 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP.blackbox.execute._ 7 | import NOP.utils._ 8 | import NOP.constants.enum._ 9 | import NOP.pipeline.core._ 10 | import NOP.builder._ 11 | import NOP.pipeline._ 12 | import NOP.pipeline.decode._ 13 | import NOP._ 14 | 15 | class MulDivExecutePlugin(val config: MyCPUConfig) extends Plugin[ExecutePipeline] { 16 | 17 | private val iqDepth = config.intIssue.depth 18 | private val rPorts = config.regFile.rPortsEachInst 19 | private val prfAddrWidth = config.regFile.prfAddrWidth 20 | 21 | val rrdReq = Vec(UInt(prfAddrWidth bits), rPorts) 22 | var rrdRsp: Vec[Bits] = null 23 | var clrBusy: Flow[UInt] = null 24 | var wPort: Flow[RegWriteBundle] = null 25 | var robWrite: Flow[UInt] = null 26 | 27 | object ISSUE_SLOT extends Stageable(MulDivIssueSlot(config)) 28 | object REG_READ_RSP extends Stageable(Vec(BWord(), rPorts)) 29 | object EXE_RESULT extends Stageable(Bits(32 bits)) 30 | 31 | override def setup(pipeline: ExecutePipeline): Unit = { 32 | val PRF = pipeline.globalService(classOf[PhysRegFilePlugin]) 33 | val ROB = pipeline.globalService(classOf[ROBFIFOPlugin]) 34 | rrdRsp = Vec(rrdReq.map(PRF.readPort(_))) 35 | clrBusy = PRF.clearBusy 36 | wPort = PRF.writePort(true) 37 | robWrite = ROB.completePort 38 | } 39 | 40 | override def build(pipeline: ExecutePipeline): Unit = { 41 | val flush = pipeline.globalService(classOf[CommitPlugin]).regFlush 42 | pipeline plug new Area { 43 | // pipeline.stages.last.arbitration.flushIt setWhen flush 44 | pipeline.stages.drop(1).foreach(_.arbitration.removeIt setWhen flush) 45 | } 46 | 47 | // ! Issue 48 | pipeline.ISS plug new Area { 49 | import pipeline.ISS._ 50 | val IQ = pipeline.globalService(classOf[MulDivIssueQueuePlugin]) 51 | require(rPorts == 2) 52 | 53 | // ISSUE ONE INSTRUCTION 54 | val QUEUE_HEAD = 0 55 | val slot = IQ.queue(QUEUE_HEAD).payload 56 | val waken = (0 until rPorts).map { j => slot.rRegs(j).valid }.andR 57 | IQ.issueReq := IQ.queue(QUEUE_HEAD).valid && waken 58 | 59 | // one-hot插入issue slot,并设置valid 60 | val issSlot = insert(ISSUE_SLOT) 61 | issSlot := IQ.queue(QUEUE_HEAD).payload 62 | val issValid = IQ.issueReq 63 | IQ.issueFire clearWhen arbitration.isStuck 64 | arbitration.removeIt setWhen (arbitration.notStuck && (flush || !issValid)) 65 | } 66 | 67 | // ! RRD 68 | pipeline.RRD plug new Area { 69 | import pipeline.RRD._ 70 | val issSlot = input(ISSUE_SLOT) 71 | for (i <- 0 until rPorts) { 72 | rrdReq(i) := issSlot.rRegs(i).payload 73 | insert(REG_READ_RSP)(i) := rrdRsp(i) 74 | } 75 | 76 | arbitration.haltByOther setWhen pipeline 77 | .globalService(classOf[SpeculativeWakeupHandler]) 78 | .regWakeupFailed 79 | } 80 | 81 | pipeline.EXE plug new Area { 82 | import pipeline.EXE._ 83 | 84 | val issSlot = input(ISSUE_SLOT) 85 | val rrdRsp = input(REG_READ_RSP) 86 | val regData = rrdRsp 87 | val exeResult = insert(EXE_RESULT) 88 | 89 | val isSigned = issSlot.uop.signed 90 | 91 | // ! Multiply 92 | val isMultiply = 93 | arbitration.isValidOnEntry && (issSlot.uop.fuType === FUType.MUL || issSlot.uop.fuType === FUType.MULH) 94 | 95 | val rj = regData(0).asSInt 96 | val rk = regData(1).asSInt 97 | val absRj = rj.abs(isSigned) 98 | val absRk = rk.abs(isSigned) 99 | 100 | val wakeupCycle = CombInit(arbitration.notStuck) 101 | 102 | val multiplier = new Multiplier() 103 | multiplier.io.A := absRj 104 | multiplier.io.B := absRk 105 | val mulResult = 106 | multiplier.io.P.twoComplement(isSigned && (rj.sign ^ rk.sign)).asBits(0, 64 bits) 107 | 108 | val mulCounter = Counter(config.mulDiv.multiplyLatency + 1) 109 | 110 | when(isMultiply) { 111 | mulCounter.increment() 112 | arbitration.haltItself setWhen (!mulCounter.willOverflowIfInc) 113 | // assume never halt by others 114 | wakeupCycle := mulCounter === config.mulDiv.multiplyLatency - 1 115 | } 116 | 117 | // ! Division 118 | val isDivision = arbitration.isValidOnEntry && 119 | (issSlot.uop.fuType === FUType.DIV || issSlot.uop.fuType === FUType.MOD) 120 | val isFirstCycle = RegNext(arbitration.notStuck) 121 | val useEarlyOut = config.mulDiv.useDivisionEarlyOut 122 | val smallDivSize = config.mulDiv.divisionEarlyOutWidth 123 | val in16Bits = 124 | if (useEarlyOut) 125 | absRj(31 downto smallDivSize) === 0 && absRk(31 downto smallDivSize) === 0 126 | else 127 | False 128 | val (quotient, remainder) = { 129 | val divider = new math.UnsignedDivider(32, 32, false) 130 | divider.io.flush := False 131 | divider.io.cmd.valid := isDivision && isFirstCycle && !in16Bits 132 | divider.io.cmd.numerator := absRj 133 | divider.io.cmd.denominator := absRk 134 | divider.io.rsp.ready := !arbitration.isStuckByOthers 135 | val absQuotient = UInt(32 bits) 136 | val absRemainder = UInt(32 bits) 137 | 138 | if (useEarlyOut) { 139 | val divider16 = new math.UnsignedDivider(16, 16, false) 140 | divider16.io.flush := False 141 | divider16.io.cmd.valid := isDivision && isFirstCycle 142 | divider16.io.cmd.numerator := absRj.resized 143 | divider16.io.cmd.denominator := absRk.resized 144 | divider16.io.rsp.ready := !arbitration.isStuckByOthers 145 | 146 | when(isDivision) { 147 | arbitration.haltItself setWhen (!in16Bits && !divider.io.rsp.valid) 148 | arbitration.haltItself setWhen (in16Bits && !divider16.io.rsp.valid) 149 | } 150 | when(in16Bits) { 151 | absQuotient := divider16.io.rsp.quotient.resized 152 | absRemainder := divider16.io.rsp.remainder.resized 153 | } otherwise { 154 | absQuotient := divider.io.rsp.quotient 155 | absRemainder := divider.io.rsp.remainder 156 | } 157 | } else { 158 | when(isDivision) { 159 | arbitration.haltItself setWhen (!divider.io.rsp.valid) 160 | } 161 | absQuotient := divider.io.rsp.quotient 162 | absRemainder := divider.io.rsp.remainder 163 | } 164 | val quotient = absQuotient.twoComplement(isSigned && (rj.sign ^ rk.sign)).asBits(0, 32 bits) 165 | val remainder = absRemainder.twoComplement(isSigned && rj.sign).asBits(0, 32 bits) 166 | (quotient, remainder) 167 | } 168 | 169 | exeResult.assignDontCare() 170 | switch(issSlot.uop.fuType) { 171 | is(FUType.MUL) { 172 | exeResult := mulResult(31 downto 0) 173 | } 174 | is(FUType.MULH) { 175 | exeResult := mulResult(63 downto 32) 176 | } 177 | is(FUType.DIV) { 178 | exeResult := quotient 179 | } 180 | is(FUType.MOD) { 181 | exeResult := remainder 182 | } 183 | } 184 | 185 | // 远程唤醒 186 | clrBusy.valid := arbitration.isValid && wakeupCycle && issSlot.uop.doRegWrite 187 | clrBusy.payload := issSlot.wReg 188 | } 189 | 190 | pipeline.WB plug new Area { 191 | import pipeline.WB._ 192 | val issSlot = input(ISSUE_SLOT) 193 | val exeResult = input(EXE_RESULT) 194 | val robIdx = issSlot.robIdx 195 | 196 | wPort.valid := arbitration.isValidNotStuck && issSlot.uop.doRegWrite 197 | wPort.payload.addr := issSlot.wReg 198 | wPort.payload.data := exeResult 199 | 200 | robWrite.valid := arbitration.isValidNotStuck 201 | robWrite.payload := robIdx 202 | 203 | } 204 | 205 | } 206 | 207 | } 208 | -------------------------------------------------------------------------------- /src/pipeline/exe/MulDivIssueQueuePlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.exe 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP._ 7 | import NOP.builder._ 8 | import NOP.utils._ 9 | import NOP.pipeline._ 10 | import NOP.pipeline.core._ 11 | import NOP.constants.enum._ 12 | 13 | class MulDivIssueQueuePlugin(config: MyCPUConfig) 14 | extends CompressedFIFO( 15 | config.mulDiv, 16 | config.decode.decodeWidth, 17 | HardType(MulDivIssueSlot(config)) 18 | ) { 19 | 20 | val rPorts = config.regFile.rPortsEachInst 21 | val busyAddrs = Vec(UInt(config.regFile.prfAddrWidth bits), decodeWidth * rPorts) 22 | 23 | def fuMatch(uop: MicroOp) = 24 | uop.fuType === FUType.MUL || uop.fuType === FUType.DIV || uop.fuType === FUType.MOD || uop.fuType === FUType.MULH 25 | 26 | override def build(pipeline: MyCPUCore): Unit = pipeline.DISPATCH plug new Area { 27 | genIssueSelect() 28 | genGlobalWakeup(pipeline.service(classOf[PhysRegFilePlugin]), rPorts) 29 | import pipeline.DISPATCH._ 30 | // 入队、唤醒与int IQ相同 31 | val decPacket = input(pipeline.decodePipeline.signals.DECODE_PACKET) 32 | val renameRecs = input(pipeline.decodePipeline.signals.RENAME_RECORDS) 33 | val robIdxs = input(pipeline.decodePipeline.signals.ROB_INDEXES) 34 | val enqueueMask = Bits(decodeWidth bits) 35 | queueIO.pushPorts.foreach(_.setIdle()) 36 | for (i <- 0 until decodeWidth) { 37 | val valid = decPacket(i).valid 38 | val uop = decPacket(i).payload 39 | val rename = renameRecs(i) 40 | 41 | // ! Push MicroOp to MulDivIssueSlot in reservation station 42 | val pushPort = queueIO.pushPorts.dataType().setBlocked() 43 | val slot = pushPort.payload 44 | slot.uop.assignAllByName(uop) 45 | for (j <- 0 until rPorts) { 46 | slot.rRegs(j).payload := rename.rRegs(j) 47 | busyAddrs(i * rPorts + j) := rename.rRegs(j) 48 | } 49 | // 入队唤醒(dispatch入口读busy) 50 | slot.rRegs(0).valid := !uop.useRj || !busyRsps(i * rPorts) 51 | slot.rRegs(1).valid := !uop.useRk || !busyRsps(i * rPorts + 1) 52 | slot.wReg := rename.wReg 53 | // ROB不关心Hi/Lo寄存器号,因为FIFO提交,但是读写寄存器需要提供这个号给Hi/Lo PRF 54 | slot.robIdx := robIdxs(i) 55 | val enqueue = valid && fuMatch(uop) && !uop.except.valid 56 | enqueueMask(i) := enqueue 57 | pushPort.valid := arbitration.isValidNotStuck && enqueue 58 | arbitration.haltItself setWhen (arbitration.isValid && enqueue && !pushPort.ready) 59 | 60 | // port互联,与rename相似 61 | if (i > 0) { 62 | val pushIdx = CountOne(enqueueMask.take(i)) 63 | for (j <- 0 to i) when(pushIdx === j && enqueueMask(i))(pushPort >> queueIO.pushPorts(j)) 64 | } else { 65 | when(enqueueMask(i))(pushPort >> queueIO.pushPorts(0)) 66 | } 67 | } 68 | 69 | // flush最高优先级 70 | val flush = pipeline.globalService(classOf[CommitPlugin]).regFlush 71 | queueFlush setWhen flush 72 | } 73 | 74 | Component.current.afterElaboration { 75 | genEnqueueLogic() 76 | genCompressLogic() 77 | genFlushLogic() 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/pipeline/fetch/BranchPredictModel.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.fetch 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP._ 7 | import NOP.utils._ 8 | import NOP.builder._ 9 | import NOP.pipeline._ 10 | 11 | final case class BranchPredictInfoBundle(withAddr: Boolean = true) extends Bundle { 12 | val predictBranch = Bool 13 | val predictTaken = Bool 14 | val predictAddr = withAddr generate UWord() 15 | } 16 | 17 | final case class PredictRecoverBundle(config: FrontendConfig) extends Bundle { 18 | val recoverTop = UInt(log2Up(config.btb.rasEntries) bits) 19 | val predictCounter = UInt(config.bpu.counterWidth bits) 20 | val ghr = UInt(config.bpu.historyWidth bits) 21 | } 22 | 23 | final case class PredictUpdateBundle(config: FrontendConfig) extends Bundle { 24 | // 原先predict信息 25 | val predInfo = BranchPredictInfoBundle(false) 26 | val predRecover = PredictRecoverBundle(config) 27 | // 实际branch计算信息 28 | val branchLike = Bool 29 | val isTaken = Bool 30 | val isRet = Bool 31 | val isCall = Bool 32 | val mispredict = Bool 33 | val pc = UWord() 34 | // 这里永远记录branch target,顺序target通过PC恢复 35 | val target = UWord() 36 | } 37 | -------------------------------------------------------------------------------- /src/pipeline/fetch/FetchBufferPlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.fetch 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP.pipeline.fetch._ 7 | import NOP.pipeline.core._ 8 | import NOP.pipeline._ 9 | import NOP.builder._ 10 | import NOP.utils._ 11 | import NOP._ 12 | 13 | final case class InstBufferEntry(config: FrontendConfig) extends Bundle { 14 | val pc = UWord() 15 | val inst = BWord() 16 | // 优化宽度,前端exception不需要bad va,永远是pc 17 | val except = Flow(ExceptionPayloadBundle(false)) 18 | val predInfo = BranchPredictInfoBundle() 19 | val predRecover = PredictRecoverBundle(config) 20 | } 21 | 22 | class FetchBufferPlugin(config: MyCPUConfig) extends Plugin[FetchPipeline] { 23 | private val frontend = config.frontend 24 | private val icache = config.frontend.icache 25 | val bufferFIFO = 26 | new MultiPortFIFOVec( 27 | InstBufferEntry(config.frontend), 28 | frontend.fetchBufferDepth, 29 | frontend.fetchWidth, 30 | config.decode.decodeWidth 31 | ) { 32 | val flush = in(Bool) 33 | when(flush) { 34 | pushPtr := 0 35 | popPtr := 0 36 | isRisingOccupancy := False 37 | // io.push.foreach(_.setBlocked()) 38 | } 39 | pushPtr.asOutput() 40 | popPtr.asOutput() 41 | } 42 | def popPorts = bufferFIFO.io.pop 43 | override def build(pipeline: FetchPipeline): Unit = pipeline.IF2 plug new Area { 44 | 45 | val flush = pipeline.globalService(classOf[CommitPlugin]).needFlush 46 | bufferFIFO.flush := flush // clear when need flush. Is this OK?? 47 | 48 | import pipeline.IF2._ 49 | import pipeline.signals._ 50 | // data有效当且仅当不超过cache line 51 | val pcWordOffset = input(PC)(icache.wordOffsetRange) 52 | val isStall = False 53 | val fetchPacket = input(pipeline.signals.FETCH_PACKET) 54 | for (i <- 0 until frontend.fetchWidth) { 55 | val p = bufferFIFO.io.push(i) 56 | val fetchWord = fetchPacket.insts(i) 57 | val fetchWordValid = fetchWord.valid && input(pipeline.signals.INSTRUCTION_MASK)(i) 58 | // 把前端异常挂给第一条指令,后面的指令没有异常,减少fan out 59 | if (i == 0) 60 | p.payload.except := fetchPacket.except 61 | else 62 | p.payload.except.setIdle() 63 | p.payload.pc := fetchPacket.pc + i * 4 // Note: i*4 is a constant 64 | p.payload.inst := fetchWord.payload 65 | 66 | // //info needed for modify btb and ras 67 | p.predInfo.predictBranch := input(pipeline.signals.BRANCH_MASK)(i) 68 | p.predInfo.predictTaken := input(pipeline.signals.TAKEN_MASK)(i) 69 | p.predInfo.predictAddr := input(PREDICT_ADDR) 70 | p.predRecover.recoverTop := input(pipeline.signals.RECOVER_TOP) 71 | p.predRecover.predictCounter := input(pipeline.signals.PRED_COUNTER)(i) 72 | p.predRecover.ghr := input(PRIVATE_BRANCH_HISTORY)(i) 73 | 74 | // 任何fetch出的word不能进buffer,整个stall住 75 | isStall setWhen (arbitration.isValid && fetchWordValid && !p.ready) 76 | p.valid := arbitration.isValidNotStuck && fetchWordValid 77 | } 78 | arbitration.haltItself setWhen isStall 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/pipeline/fetch/FetchDebug.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.fetch 2 | 3 | import NOP.blackbox.mem._ 4 | import NOP.pipeline._ 5 | import NOP.builder._ 6 | import NOP.utils._ 7 | import NOP._ 8 | import NOP.constants.`enum`.{CacheOpType, CacheSelType} 9 | import NOP.pipeline.core.{CommitPlugin, ExceptionMuxPlugin} 10 | import spinal.core._ 11 | import spinal.lib._ 12 | import spinal.lib.bus.amba4.axi._ 13 | import spinal.lib.fsm._ 14 | 15 | class FetchDebugPlugin(config: MyCPUConfig) extends Plugin[FetchPipeline] { 16 | // New signals for debug 17 | val DIF1_valid = out Bool () 18 | val DIF1_stuck = out Bool () 19 | val DIF1_doRefetch = out Bool () 20 | val DIF1_pc = out Bits (32 bits) 21 | val DIF1_nextpc = out Bits (32 bits) 22 | val DIF1_directTranslate_physAddr = out Bits (32 bits) 23 | val DIF1_directTranslate_enabled = out Bool () 24 | val DIF1_tlbTranslate_physAddr = out Bits (32 bits) 25 | val DIF2_valid = out Bool () 26 | val DIF2_stuck = out Bool () 27 | val DIF2_pc = out Bits (32 bits) 28 | val DIF2_directTranslate_enabled = out Bool () 29 | val DIF2_directTranslate_physAddr = out Bits (32 bits) 30 | val DIF2_tlbTranslate_physAddr = out Bits (32 bits) 31 | val DIF2_physAddr = out Bits (32 bits) 32 | val DIF2_fetchPacket_payload_valid = out Bits (config.frontend.fetchWidth bits) 33 | val DIF2_fetchPacket_payload_insts = out Vec (Bits(32 bits), config.frontend.fetchWidth) 34 | 35 | override def build(pipeline: FetchPipeline): Unit = { 36 | val ICachePlugin = pipeline.service(classOf[ICachePlugin]) 37 | val programCounterPlugin = pipeline.service(classOf[ProgramCounterPlugin]) 38 | 39 | DIF1_valid := pipeline.IF1.arbitration.isValid 40 | DIF1_stuck := pipeline.IF1.arbitration.isStuck 41 | DIF1_doRefetch := False 42 | DIF1_pc := pipeline.IF1.output(pipeline.signals.PC).asBits 43 | DIF1_nextpc := programCounterPlugin.nextPC.asBits 44 | DIF1_directTranslate_physAddr := pipeline.IF1 45 | .output(pipeline.signals.DIRECT_TRANSLATE_RESULT) 46 | .payload 47 | .physAddr 48 | .asBits 49 | DIF1_directTranslate_enabled := pipeline.IF1.output(pipeline.signals.DIRECT_TRANSLATE_RESULT).valid 50 | DIF1_tlbTranslate_physAddr := pipeline.IF1.output(pipeline.signals.TLB_TRANSLATE_RESULT).payload.physAddr.asBits 51 | DIF2_valid := pipeline.IF2.arbitration.isValid 52 | DIF2_stuck := pipeline.IF2.arbitration.isStuck 53 | DIF2_pc := pipeline.IF2.input(pipeline.signals.PC).asBits 54 | DIF2_directTranslate_enabled := pipeline.IF2.input(pipeline.signals.DIRECT_TRANSLATE_RESULT).valid 55 | DIF2_directTranslate_physAddr := pipeline.IF2 56 | .input(pipeline.signals.DIRECT_TRANSLATE_RESULT) 57 | .payload 58 | .physAddr 59 | .asBits 60 | DIF2_tlbTranslate_physAddr := pipeline.IF2.input(pipeline.signals.TLB_TRANSLATE_RESULT).payload.physAddr.asBits 61 | DIF2_physAddr := pipeline.IF2.output(pipeline.signals.PC_PHYSICAL).asBits 62 | DIF2_fetchPacket_payload_valid := pipeline.IF2 63 | .output(pipeline.signals.FETCH_PACKET) 64 | .insts 65 | .map { inst => inst.valid } 66 | .asBits() 67 | DIF2_fetchPacket_payload_insts := Vec( 68 | pipeline.IF2.output(pipeline.signals.FETCH_PACKET).insts.map { inst => inst.payload } 69 | ) 70 | 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/pipeline/fetch/FetchPipeline.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.fetch 2 | 3 | import NOP._ 4 | import NOP.utils._ 5 | import NOP.builder._ 6 | import NOP.pipeline._ 7 | import NOP.pipeline.priviledge._ 8 | 9 | import spinal.core._ 10 | import spinal.lib._ 11 | import spinal.lib.bus.amba4.axi._ 12 | 13 | // Definition of Signals and Pipeline 14 | 15 | class FetchSignals(config: MyCPUConfig) { 16 | private val fetchWidth = config.frontend.fetchWidth 17 | 18 | // Inner Usage 19 | object PC extends Stageable(UWord()) // ProgramCounterPlugin 20 | object NEXT_PC extends Stageable(UWord()) // ProgramCounterPlugin 21 | object PC_PHYSICAL extends Stageable(UWord()) // AddrTranslatePlugin 22 | object ADDRESS_CACHED extends Stageable(Bool()) 23 | object IS_TLB_REFILL extends Stageable(Bool()) 24 | 25 | // ADDR 26 | object DIRECT_TRANSLATE_RESULT extends Stageable(Flow(TranslateResultBundle())) 27 | object TLB_TRANSLATE_RESULT extends Stageable(Flow(TranslateResultBundle())) 28 | object TRANSLATE_SAVED_CSR extends Stageable(TranslateCSRBundle()) 29 | 30 | // BTB & BPU & RAS 31 | object PREDICT_JUMP_FLAG extends Stageable(Bool()) 32 | object PREDICT_JUMP_PAYLOAD extends Stageable(BranchStatusPayload()) 33 | object PREDICT_JUMP_WAY extends Stageable(UInt(log2Up(config.frontend.fetchWidth) bits)) 34 | object INSTRUCTION_MASK extends Stageable(Bits(fetchWidth bits)) 35 | object BRANCH_MASK extends Stageable(Bits(fetchWidth bits)) 36 | object TAKEN_MASK extends Stageable(Bits(fetchWidth bits)) 37 | object PRED_COUNTER extends Stageable(Vec(UInt(config.frontend.bpu.counterWidth bits), fetchWidth)) 38 | object GLOBAL_BRANCH_HISTORY extends Stageable(UInt(config.frontend.bpu.historyWidth bits)) 39 | object PRIVATE_BRANCH_HISTORY extends Stageable(Vec(UInt(config.frontend.bpu.historyWidth bits), fetchWidth)) 40 | object PREDICT_ADDR extends Stageable(UWord()) 41 | object RECOVER_TOP extends Stageable(UInt(log2Up(config.frontend.btb.rasEntries) bits)) 42 | 43 | // Outer Usage 44 | object FETCH_PACKET extends Stageable(FetchPacket(fetchWidth)) 45 | } 46 | 47 | trait FetchPipeline extends Pipeline { 48 | type T = FetchPipeline 49 | val IF1: Stage = null 50 | val IF2: Stage = null 51 | val signals: FetchSignals 52 | } 53 | -------------------------------------------------------------------------------- /src/pipeline/fetch/FetchSignals.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.fetch 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import NOP.utils._ 6 | import NOP._ 7 | import NOP.pipeline.core._ 8 | 9 | final case class FetchPacket(fetchWidth: Int) extends Bundle { 10 | val pc = UWord() 11 | val insts = Vec(Flow(BWord()), fetchWidth) 12 | // 优化宽度,前端 exception 不需要 bad va,永远是 pc 13 | val except = Flow(ExceptionPayloadBundle(false)) 14 | } 15 | 16 | case class BranchStatusPayload() extends Bundle { 17 | val target = UInt(30 bits) 18 | val isCall = Bool() 19 | val isReturn = Bool() 20 | } 21 | 22 | case class BranchTableEntry(config: BTBConfig) extends Bundle { 23 | val tag = UInt(config.tagRange.size bits) 24 | val statusBundle = BranchStatusPayload() 25 | } 26 | -------------------------------------------------------------------------------- /src/pipeline/fetch/InstAddrTranslationPlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.fetch 2 | 3 | import NOP.builder._ 4 | import NOP.utils._ 5 | import NOP.constants._ 6 | import NOP._ 7 | import NOP.constants.`enum`.MemOperationType 8 | import NOP.pipeline._ 9 | import NOP.pipeline.core.ExceptionMuxPlugin 10 | import NOP.pipeline.priviledge.{ExceptionHandlerPlugin, MMUPlugin} 11 | import spinal.core._ 12 | import spinal.lib._ 13 | import spinal.lib.bus.amba4.axi._ 14 | import spinal.lib.fsm._ 15 | import NOP.pipeline.priviledge._ 16 | 17 | class InstAddrTranslatePlugin() extends Plugin[FetchPipeline] { 18 | val PIF = Bool() 19 | val PPI = Bool() 20 | val ADEF = Bool() 21 | val TLBR = Bool() 22 | val badVaddr = UInt(32 bits) 23 | val badVaddr2 = UInt(32 bits) 24 | 25 | override def setup(pipeline: FetchPipeline): Unit = { 26 | import LoongArch.ExceptionCode 27 | val iExec = pipeline.service(classOf[ExceptionMuxPlugin[FetchPipeline]]) 28 | iExec.addExceptionSource(pipeline.IF1, ExceptionCode.ADEF, ADEF, badVaddr, priority = 100) 29 | iExec.addExceptionSource(pipeline.IF2, ExceptionCode.TLBR, TLBR, badVaddr2, priority = 90) 30 | iExec.addExceptionSource(pipeline.IF2, ExceptionCode.PIF, PIF, badVaddr2, priority = 80) 31 | iExec.addExceptionSource(pipeline.IF2, ExceptionCode.PPI, PPI, badVaddr2, priority = 70) 32 | } 33 | 34 | override def build(pipeline: FetchPipeline): Unit = { 35 | val mmu = pipeline.globalService(classOf[MMUPlugin]) 36 | val excHandler = pipeline.globalService(classOf[ExceptionHandlerPlugin]) 37 | 38 | pipeline.IF1 plug new Area { 39 | import pipeline.IF1._ 40 | import pipeline.signals._ 41 | 42 | // fetch packet不跨行,更不跨页 43 | val virtPC = input(PC) 44 | badVaddr := input(PC) 45 | ADEF := (input(PC)(0) || input(PC)(1)) 46 | 47 | val directTranslateResult = mmu.directTranslate(virtPC, MemOperationType.FETCH) 48 | val tlbTranslateResult = mmu.tlbTranslate(virtPC, MemOperationType.FETCH) 49 | val savedCSR = TranslateCSRBundle() 50 | savedCSR.CRMD_DA := excHandler.CRMD_DA 51 | savedCSR.CRMD_PG := excHandler.CRMD_PG 52 | savedCSR.CRMD_DATF := excHandler.CRMD_DATF 53 | savedCSR.CRMD_DATM := excHandler.CRMD_DATM 54 | 55 | insert(DIRECT_TRANSLATE_RESULT) := directTranslateResult.resultBundle 56 | insert(TLB_TRANSLATE_RESULT) := tlbTranslateResult.resultBundle 57 | insert(TRANSLATE_SAVED_CSR) := savedCSR 58 | } 59 | 60 | pipeline.IF2 plug new Area { 61 | import pipeline.IF2._ 62 | import pipeline.signals._ 63 | 64 | val virtPC = input(PC) 65 | badVaddr2 := input(PC) 66 | 67 | val physPC = insert(PC_PHYSICAL) 68 | val pcCached = insert(ADDRESS_CACHED) 69 | val tlbRefill = insert(IS_TLB_REFILL) 70 | 71 | val translateResult = mmu.translate( 72 | virtPC, 73 | MemOperationType.FETCH, 74 | input(DIRECT_TRANSLATE_RESULT), 75 | input(TLB_TRANSLATE_RESULT), 76 | input(TRANSLATE_SAVED_CSR) 77 | ) 78 | PIF := translateResult.resultExceptionBundle.raisePIF 79 | PPI := translateResult.resultExceptionBundle.raisePPI 80 | TLBR := translateResult.resultExceptionBundle.raiseTLBR 81 | 82 | physPC := translateResult.resultPhysAddr 83 | pcCached := translateResult.resultCached 84 | tlbRefill := TLBR 85 | 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/pipeline/fetch/ProgramCounterPlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.fetch 2 | 3 | import NOP.pipeline._ 4 | import NOP.builder._ 5 | import NOP.utils._ 6 | import NOP._ 7 | 8 | import spinal.core._ 9 | import spinal.lib._ 10 | import scala.collection.mutable.ArrayBuffer 11 | 12 | class ProgramCounterPlugin(config: FrontendConfig) extends Plugin[FetchPipeline] { 13 | case class JumpInfo(interface: Stream[UInt], priority: Int) 14 | private val jumpInfos = ArrayBuffer[JumpInfo]() 15 | private var predict: Flow[UInt] = null 16 | 17 | // * outside functions 18 | // add a new entry to the jumpInfo, the larger the higher priority 19 | def addJumpInterface(interface: Stream[UInt], priority: Int): Unit = { 20 | jumpInfos += JumpInfo(interface, priority) 21 | } 22 | 23 | def setPredict(source: Flow[UInt]): Unit = { 24 | predict = source 25 | } 26 | 27 | // * inner signals 28 | val nextPC = UWord() 29 | val backendJumpInterface = Stream(UWord()).setIdle() 30 | override def build(pipeline: FetchPipeline): Unit = pipeline.IF1 plug new Area { 31 | import pipeline.IF1._ 32 | val jumpPipe = backendJumpInterface.s2mPipe() 33 | jumpPipe.ready := !arbitration.isStuck 34 | val cacheLineWords = config.icache.lineWords 35 | 36 | // ! declare regPC 37 | val regPC = RegNextWhen(nextPC, !arbitration.isStuck, init = UWord(config.pcInit)) 38 | insert(pipeline.signals.PC) := regPC 39 | 40 | // ! Set nextPC 41 | // ! nextPC branch 1, increment 42 | val pcOffset = regPC(2, log2Up(cacheLineWords) bits) 43 | val fetchWidth = config.fetchWidth 44 | 45 | val defaultPC = regPC + (pcOffset.muxList( 46 | U(fetchWidth), // default: increment by fetchWidth 47 | ((cacheLineWords - fetchWidth + 1) until cacheLineWords).map { i => 48 | (i, U(cacheLineWords - i)) // 一次fetch不允许跨行,因此最多顶到行尾. 顶到行尾的时候,从下一行开始 49 | } 50 | ) @@ U"2'b00") 51 | nextPC := defaultPC 52 | 53 | // ! nextPC branch 2, BTB prediction jump 54 | if (predict != null) { 55 | when(predict.valid)(nextPC := predict.payload) 56 | } 57 | 58 | // ! nextPC branch 3 (highest priority), backend jump 59 | when(jumpPipe.valid)(nextPC := jumpPipe.payload) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/pipeline/fetch/ReturnAddressStackPlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.fetch 2 | 3 | import NOP.pipeline._ 4 | import NOP.builder._ 5 | import NOP.utils._ 6 | import NOP._ 7 | import NOP.pipeline.core.CommitPlugin 8 | import spinal.core._ 9 | import spinal.lib._ 10 | 11 | import scala.collection.mutable.ArrayBuffer 12 | 13 | class ReturnAddressStackPlugin(config: FrontendConfig) extends Plugin[FetchPipeline] { 14 | require(isPow2(config.btb.rasEntries)) 15 | val ras = Vec(RegInit(U(config.pcInit >> 2, 30 bits)), config.btb.rasEntries) 16 | val rasTopEntry = RegInit(U(0, log2Up(config.btb.rasEntries) bits)) 17 | val rasPredict = Flow(UWord()).setIdle() 18 | 19 | override def build(pipeline: FetchPipeline): Unit = { 20 | pipeline.IF2 plug new Area { 21 | 22 | import pipeline.IF2._ 23 | import pipeline.signals._ 24 | 25 | val jumpFlag = input(PREDICT_JUMP_FLAG) 26 | val jumpPayload = input(PREDICT_JUMP_PAYLOAD) 27 | val jumpWay = input(PREDICT_JUMP_WAY) 28 | 29 | insert(RECOVER_TOP) := rasTopEntry // set Recover top for every instruction 30 | 31 | when(arbitration.isValid && !arbitration.isStuck && jumpFlag) { // update ras when call or return is found 32 | // note that ras shouldn't be updated when fetching delay slot 33 | // note that ras predict update logic is triggered only when valid and firing 34 | when(jumpPayload.isCall) { 35 | ras(rasTopEntry + 1) := input(PC)(2, 30 bits) + jumpWay + 1 36 | rasTopEntry := rasTopEntry + 1 37 | } elsewhen (jumpPayload.isReturn) { 38 | rasTopEntry := rasTopEntry - 1 39 | rasPredict.push(ras(rasTopEntry) @@ U(0, 2 bits)) 40 | } 41 | } 42 | } 43 | 44 | pipeline plug new Area { 45 | // commit area 46 | val bpuCommit = pipeline.globalService(classOf[CommitPlugin]) 47 | val predUpdate = bpuCommit.predUpdate 48 | 49 | when(predUpdate.valid) { // time to update 50 | val payload = predUpdate.payload 51 | val pred = payload.predInfo 52 | val recover = payload.predRecover 53 | when(pred.predictBranch && !payload.branchLike) { 54 | // Case 1, modified instruction, not branch anymore, must be mispredicted 55 | rasTopEntry := recover.recoverTop 56 | } 57 | 58 | when(payload.mispredict) { // ras is the only thing need to recover when mispredict 59 | when(payload.isCall) { 60 | rasTopEntry := recover.recoverTop + 1 61 | ras(recover.recoverTop + 1) := payload.pc(31 downto 2) + 1 62 | } elsewhen (payload.isRet) { 63 | rasTopEntry := recover.recoverTop - 1 64 | } otherwise { 65 | rasTopEntry := recover.recoverTop 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/pipeline/mem/AddressGenerationPlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.mem 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import NOP.utils._ 6 | import NOP.constants.enum._ 7 | import NOP.pipeline.core._ 8 | import NOP.builder._ 9 | import NOP.pipeline._ 10 | import NOP.pipeline.decode._ 11 | import NOP._ 12 | import NOP.pipeline.exe.MemIssueQueuePlugin 13 | import NOP.pipeline.priviledge._ 14 | 15 | class AddressGenerationPlugin(config: MyCPUConfig) extends Plugin[MemPipeline] { 16 | 17 | val raisePIL = Bool() 18 | val raisePIS = Bool() 19 | val raisePME = Bool() 20 | val raisePPI = Bool() 21 | val raiseALE = False 22 | val raiseTLBR = Bool() 23 | 24 | override def setup(pipeline: MemPipeline): Unit = { 25 | import NOP.constants.LoongArch.ExceptionCode 26 | val iExec = pipeline.service(classOf[ExceptionMuxPlugin[MemPipeline]]) 27 | 28 | iExec.addExceptionSource(pipeline.MEMADDR, ExceptionCode.ALE, raiseALE, priority = 130) 29 | iExec.addExceptionSource(pipeline.MEM1, ExceptionCode.TLBR, raiseTLBR, priority = 120) 30 | iExec.addExceptionSource(pipeline.MEM1, ExceptionCode.PIL, raisePIL, priority = 115) 31 | iExec.addExceptionSource(pipeline.MEM1, ExceptionCode.PIS, raisePIS, priority = 110) 32 | iExec.addExceptionSource(pipeline.MEM1, ExceptionCode.PPI, raisePPI, priority = 105) 33 | iExec.addExceptionSource(pipeline.MEM1, ExceptionCode.PME, raisePME, priority = 100) 34 | 35 | } 36 | 37 | override def build(pipeline: MemPipeline): Unit = { 38 | val excHandler = pipeline.globalService(classOf[ExceptionHandlerPlugin]) 39 | pipeline.MEMADDR plug new Area { 40 | 41 | import pipeline.MEMADDR._ 42 | import pipeline.signals._ 43 | 44 | val issSlot = input(ISSUE_SLOT) 45 | val uop = issSlot.uop 46 | val virtAddr = input(MEMORY_ADDRESS) 47 | val accessType = uop.lsType 48 | val isLoad = uop.isLoad 49 | val excAsLoad = !uop.isStore 50 | val isStore = uop.isStore 51 | val byteEnable = insert(MEMORY_BE).assignDontCare() 52 | val memWData = output(MEMORY_WRITE_DATA).allowOverride 53 | val dataWord = input(MEMORY_WRITE_DATA) 54 | 55 | // byte enable调整为对齐访问的,但是addr后两位保留,load后处理好写一些 56 | // 因此addr后两位对于实际load/store要忽略掉 57 | switch(accessType) { 58 | import LoadStoreType._ 59 | is(BYTE, BYTE_U, CACOP, PRELD) { 60 | byteEnable := virtAddr(1 downto 0).mux( 61 | 0 -> B"4'b0001", 62 | 1 -> B"4'b0010", 63 | 2 -> B"4'b0100", 64 | 3 -> B"4'b1000" 65 | ) 66 | memWData := virtAddr(1 downto 0).muxList(Seq.tabulate(4) { i => 67 | i -> (dataWord |<< (i * 8)) 68 | }) 69 | } 70 | is(HALF, HALF_U) { 71 | raiseALE.setWhen(virtAddr(0)) 72 | byteEnable := Mux(virtAddr(1), B"4'b1100", B"4'b0011") 73 | memWData := Mux(virtAddr(1), dataWord |<< 16, dataWord) 74 | } 75 | is(WORD) { 76 | raiseALE.setWhen(virtAddr(1 downto 0) =/= 0) 77 | byteEnable := 0xf 78 | memWData := dataWord 79 | } 80 | } 81 | // unaligned load用WDATA传rt的读结果,不可改变;aligned load不使用WDATA 82 | when(isLoad)(memWData := input(MEMORY_WRITE_DATA)) 83 | 84 | // translate 85 | val mmu = pipeline.globalService(classOf[MMUPlugin]) 86 | val directTranslateResult = 87 | mmu.directTranslate(virtAddr, Mux(isStore, MemOperationType.STORE, MemOperationType.LOAD)) 88 | val tlbTranslateResult = mmu.tlbTranslate(virtAddr, Mux(isStore, MemOperationType.STORE, MemOperationType.LOAD)) 89 | val savedCSR = TranslateCSRBundle() 90 | savedCSR.CRMD_DA := excHandler.CRMD_DA 91 | savedCSR.CRMD_PG := excHandler.CRMD_PG 92 | savedCSR.CRMD_DATF := excHandler.CRMD_DATF 93 | savedCSR.CRMD_DATM := excHandler.CRMD_DATM 94 | 95 | insert(DIRECT_TRANSLATE_RESULT) := directTranslateResult.resultBundle 96 | insert(TLB_TRANSLATE_RESULT) := tlbTranslateResult.resultBundle 97 | insert(TRANSLATE_SAVED_CSR) := savedCSR 98 | } 99 | 100 | pipeline.MEM1 plug new Area { 101 | 102 | import pipeline.MEM1._ 103 | import pipeline.signals._ 104 | 105 | val issSlot = input(ISSUE_SLOT) 106 | val uop = issSlot.uop 107 | val virtAddr = input(MEMORY_ADDRESS) 108 | val accessType = uop.lsType 109 | val isLoad = uop.isLoad 110 | val excAsLoad = !uop.isStore 111 | val isStore = uop.isStore 112 | val physAddr = insert(MEMORY_ADDRESS_PHYSICAL) 113 | 114 | val MMU = pipeline.globalService(classOf[MMUPlugin]) 115 | val translateResult = MMU.translate( 116 | virtAddr, 117 | Mux(isStore, MemOperationType.STORE, MemOperationType.LOAD), 118 | input(DIRECT_TRANSLATE_RESULT), 119 | input(TLB_TRANSLATE_RESULT), 120 | input(TRANSLATE_SAVED_CSR) 121 | ) 122 | insert(ADDRESS_CACHED) := translateResult.resultBundle.cached 123 | physAddr := translateResult.resultBundle.physAddr 124 | insert(IS_TLB_REFILL) := translateResult.resultExceptionBundle.raiseTLBR 125 | 126 | raisePIL := translateResult.resultExceptionBundle.raisePIL 127 | raisePIS := translateResult.resultExceptionBundle.raisePIS 128 | raisePME := translateResult.resultExceptionBundle.raisePME 129 | raisePPI := translateResult.resultExceptionBundle.raisePPI 130 | raiseTLBR := translateResult.resultExceptionBundle.raiseTLBR 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/pipeline/mem/DirtyBitsManager.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.mem 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import spinal.lib.fsm._ 6 | import spinal.lib.bus.amba4.axi.Axi4 7 | 8 | import NOP._ 9 | import NOP.blackbox.mem._ 10 | import NOP.builder._ 11 | import NOP.utils._ 12 | import NOP.pipeline._ 13 | import NOP.pipeline.core._ 14 | import NOP.constants.enum._ 15 | 16 | class DirtyBitsManager(config: CacheBasicConfig) extends Component { 17 | val io = new Bundle { 18 | val readCmd = in(UInt(config.indexWidth bits)) 19 | val readRsp = out(Bits(config.ways bits)) 20 | val writeCmd = in(Flow(new Bundle { 21 | val idx = UInt(config.indexWidth bits) 22 | val way = UInt(log2Up(config.ways) bits) 23 | val data = Bool() 24 | })) 25 | } 26 | val dirtyBits = Vec(RegInit(B(0, config.ways bits)), config.sets) 27 | // normal read 28 | io.readRsp := dirtyBits(io.readCmd) 29 | // normal write 30 | when(io.writeCmd.valid) { 31 | dirtyBits(io.writeCmd.idx)(io.writeCmd.way) := io.writeCmd.data 32 | } 33 | // write -> read bypass 34 | when(io.writeCmd.valid && io.readCmd === io.writeCmd.idx) { 35 | io.readRsp(io.writeCmd.way) := io.writeCmd.data 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/pipeline/mem/LoadPostProcessPlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.mem 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import NOP.utils._ 6 | import NOP.constants.enum._ 7 | import NOP.pipeline.core._ 8 | import NOP.builder._ 9 | import NOP.pipeline._ 10 | import NOP.pipeline.decode._ 11 | import NOP._ 12 | import NOP.pipeline.exe.MemIssueQueuePlugin 13 | 14 | /** 拼出输出word。 15 | */ 16 | class LoadPostprocessPlugin extends Plugin[MemPipeline] { 17 | override def build(pipeline: MemPipeline): Unit = pipeline.MEM2 plug new Area { 18 | import pipeline.MEM2._ 19 | import pipeline.signals._ 20 | val std = input(STD_SLOT) 21 | val accessType = LoadStoreType() 22 | // 这里用VA或PA都一样 23 | val wordOffset = UInt(2 bits) 24 | val rtData = BWord() 25 | val readWord = input(MEMORY_READ_DATA) 26 | val wbWord = output(MEMORY_READ_DATA).allowOverride 27 | when(std.valid) { 28 | // 必然不与load一起,LDU需要load postprocess 29 | accessType := std.lsType 30 | wordOffset := std.addr(1 downto 0) 31 | rtData := std.data 32 | } otherwise { 33 | // load使用load postprocess 34 | accessType := input(LOAD_STORE_TYPE) 35 | // 这里用VA或PA都一样 36 | wordOffset := input(MEMORY_ADDRESS_PHYSICAL)(1 downto 0) 37 | rtData := input(MEMORY_WRITE_DATA) 38 | } 39 | switch(accessType) { 40 | import LoadStoreType._ 41 | is(BYTE) { 42 | wbWord := wordOffset 43 | .muxList(Seq.tabulate(4) { i => i -> readWord(i * 8, 8 bits) }) 44 | .asSInt 45 | .resize(32 bits) 46 | .asBits 47 | } 48 | is(BYTE_U) { 49 | wbWord := wordOffset 50 | .muxList(Seq.tabulate(4) { i => i -> readWord(i * 8, 8 bits) }) 51 | .asUInt 52 | .resize(32 bits) 53 | .asBits 54 | } 55 | is(HALF) { 56 | wbWord := Mux(wordOffset(1), readWord(16, 16 bits), readWord(0, 16 bits)).asSInt 57 | .resize(32 bits) 58 | .asBits 59 | } 60 | is(HALF_U) { 61 | wbWord := Mux(wordOffset(1), readWord(16, 16 bits), readWord(0, 16 bits)).asUInt 62 | .resize(32 bits) 63 | .asBits 64 | } 65 | is(WORD) { 66 | wbWord := readWord 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/pipeline/mem/MemIssueQueuePlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.exe 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | import NOP._ 7 | import NOP.builder._ 8 | import NOP.utils._ 9 | import NOP.pipeline._ 10 | import NOP.pipeline.core._ 11 | import NOP.constants.enum._ 12 | 13 | class MemIssueQueuePlugin(config: MyCPUConfig) 14 | extends CompressedFIFO( 15 | config.memIssue, 16 | config.decode.decodeWidth, 17 | HardType(MemIssueSlot(config)) 18 | ) { 19 | private val issConfig = config.memIssue 20 | val rPorts = config.regFile.rPortsEachInst 21 | val busyAddrs = Vec(UInt(config.regFile.prfAddrWidth bits), decodeWidth * rPorts) 22 | def fuMatch(uop: MicroOp) = uop.fuType === FUType.LSU 23 | 24 | def build(pipeline: MyCPUCore): Unit = pipeline.DISPATCH plug new Area { 25 | genIssueSelect() 26 | genGlobalWakeup(pipeline.service(classOf[PhysRegFilePlugin]), rPorts) 27 | import pipeline.DISPATCH._ 28 | // 入队、唤醒与int IQ相同 29 | val decPacket = input(pipeline.decodePipeline.signals.DECODE_PACKET) 30 | val renameRecs = input(pipeline.decodePipeline.signals.RENAME_RECORDS) 31 | val robIdxs = input(pipeline.decodePipeline.signals.ROB_INDEXES) 32 | val enqueueMask = Bits(decodeWidth bits) 33 | queueIO.pushPorts.foreach(_.setIdle()) 34 | for (i <- 0 until decodeWidth) { 35 | val valid = decPacket(i).valid 36 | val uop = decPacket(i).payload 37 | val rename = renameRecs(i) 38 | val pushPort = queueIO.pushPorts.dataType().setBlocked() 39 | val slot = pushPort.payload 40 | 41 | slot.uop.assignSomeByName(uop) 42 | slot.uop.immField := uop.inst(21 downto 10).asSInt.resize(32 bits).asBits 43 | when(slot.uop.isSC || slot.uop.isLL) { 44 | slot.uop.immField := (uop.inst(23 downto 10) ## U(0x0, 2 bits)).asSInt.resize(32 bits).asBits 45 | } 46 | 47 | // ! Special support decoding for CACOP and PRELD 48 | switch(uop.lsType) { 49 | is(LoadStoreType.CACOP) { 50 | switch(uop.inst(4 downto 3)) { 51 | is(0x0) { 52 | slot.uop.cacheOp := CacheOpType.StoreTag 53 | } 54 | is(0x1) { 55 | slot.uop.cacheOp := CacheOpType.IndexInvalidate 56 | } 57 | is(0x2) { 58 | slot.uop.cacheOp := CacheOpType.HitInvalidate 59 | } 60 | default { 61 | slot.uop.cacheOp := CacheOpType.None 62 | } 63 | } 64 | 65 | switch(uop.inst(2 downto 0)) { 66 | is(0x0) { 67 | slot.uop.cacheSel := CacheSelType.ICache 68 | } 69 | is(0x1) { 70 | slot.uop.cacheSel := CacheSelType.DCache 71 | } 72 | default { 73 | slot.uop.cacheSel := CacheSelType.None 74 | } 75 | } 76 | } 77 | is(LoadStoreType.PRELD) { 78 | slot.uop.cacheOp := CacheOpType.None 79 | switch(uop.inst(4 downto 0)) { 80 | is(0x0) { 81 | slot.uop.cacheSel := CacheSelType.ICache 82 | } 83 | is(0x8) { 84 | slot.uop.cacheSel := CacheSelType.DCache 85 | } 86 | default { 87 | slot.uop.cacheSel := CacheSelType.None 88 | } 89 | } 90 | 91 | } 92 | default { 93 | slot.uop.cacheOp := CacheOpType.None 94 | slot.uop.cacheSel := CacheSelType.DCache 95 | } 96 | } 97 | 98 | for (j <- 0 until rPorts) { 99 | slot.rRegs(j).payload := rename.rRegs(j) 100 | busyAddrs(i * rPorts + j) := rename.rRegs(j) 101 | } 102 | // 入队唤醒(dispatch入口读busy) 103 | slot.rRegs(0).valid := !uop.useRj || !busyRsps(i * rPorts) 104 | slot.rRegs(1).valid := !(uop.useRk || uop.useRd) || !busyRsps(i * rPorts + 1) 105 | slot.wReg := rename.wReg 106 | slot.robIdx := robIdxs(i) 107 | val enqueue = valid && fuMatch(uop) && !uop.except.valid 108 | enqueueMask(i) := enqueue 109 | pushPort.valid := arbitration.isValidNotStuck && enqueue 110 | arbitration.haltItself setWhen (arbitration.isValid && enqueue && !pushPort.ready) 111 | 112 | // port互联,与rename相似 113 | if (i > 0) { 114 | val pushIdx = CountOne(enqueueMask.take(i)) 115 | for (j <- 0 to i) when(pushIdx === j && enqueueMask(i))(pushPort >> queueIO.pushPorts(j)) 116 | } else { 117 | when(enqueueMask(i))(pushPort >> queueIO.pushPorts(0)) 118 | } 119 | } 120 | 121 | // flush最高优先级 122 | val flush = pipeline.globalService(classOf[CommitPlugin]).regFlush 123 | queueFlush setWhen flush 124 | 125 | } 126 | Component.current.afterElaboration { 127 | genEnqueueLogic() 128 | genCompressLogic() 129 | genFlushLogic() 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/pipeline/mem/MemPipeline.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.mem 2 | 3 | import NOP.pipeline.priviledge._ 4 | import NOP.constants.enum._ 5 | import NOP.builder._ 6 | import NOP.pipeline._ 7 | import NOP.utils._ 8 | import NOP._ 9 | 10 | import spinal.core._ 11 | import spinal.lib._ 12 | 13 | class MemSignals(config: MyCPUConfig) { 14 | private val dcache = config.dcache 15 | private val iqDepth = config.memIssue.depth 16 | private val rPorts = config.regFile.rPortsEachInst 17 | private val prfAddrWidth = config.regFile.prfAddrWidth 18 | 19 | object ISSUE_SLOT extends Stageable(MemIssueSlot(config)) 20 | object REG_READ_RSP extends Stageable(Vec(BWord(), rPorts)) 21 | object WRITE_REG extends Stageable(Flow(UInt(prfAddrWidth bits))) 22 | object READ_REGS extends Stageable(Vec(UInt(prfAddrWidth bits), 2)) 23 | object ROB_IDX extends Stageable(UInt(config.rob.robAddressWidth bits)) 24 | object STD_SLOT extends Stageable(Flow(StoreBufferSlot(config))) 25 | 26 | object MEMORY_ADDRESS extends Stageable(UWord()) 27 | object MEMORY_ADDRESS_PHYSICAL extends Stageable(UWord()) 28 | object MEMORY_BE extends Stageable(Bits(4 bits)) 29 | object MEMORY_READ_DATA extends Stageable(BWord()) 30 | object MEMORY_WRITE_DATA extends Stageable(BWord()) 31 | object TMP_RESULT extends Stageable(BWord()) 32 | 33 | object DIRECT_TRANSLATE_RESULT extends Stageable(Flow(TranslateResultBundle())) 34 | object TLB_TRANSLATE_RESULT extends Stageable(Flow(TranslateResultBundle())) 35 | object TRANSLATE_SAVED_CSR extends Stageable(TranslateCSRBundle()) 36 | 37 | object ADDRESS_CACHED extends Stageable(Bool()) 38 | object IS_TLB_REFILL extends Stageable(Bool()) 39 | object LOAD_STORE_TYPE extends Stageable(LoadStoreType()) 40 | object IS_LOAD extends Stageable(Bool()) 41 | object IS_STORE extends Stageable(Bool()) 42 | } 43 | 44 | trait MemPipeline extends Pipeline { 45 | type T = MemPipeline 46 | val ISS: Stage = null // issue/select 47 | val RRD: Stage = null // register read 48 | val MEMADDR: Stage = null 49 | val MEM1: Stage = null 50 | val MEM2: Stage = null 51 | val WB: Stage = null 52 | val WB2: Stage = null // 注意读cache需要预留两个周期的冲突,所以增加一个空的WB阶段,用来前传 53 | val signals: MemSignals 54 | } 55 | -------------------------------------------------------------------------------- /src/pipeline/mem/StoreBufferPlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.mem 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import NOP._ 6 | import NOP.utils._ 7 | import NOP.builder._ 8 | import NOP.pipeline._ 9 | import NOP.constants.enum._ 10 | import NOP.pipeline.core.CommitPlugin 11 | 12 | final case class StoreBufferSlot(config: MyCPUConfig) extends Bundle { 13 | val retired = Bool() 14 | val addr = UWord() 15 | val be = Bits(4 bits) 16 | val data = BWord() 17 | // extension: accept uncached load/store 18 | val isStore = Bool() 19 | val isCached = Bool() 20 | val wReg = Flow(UInt(config.regFile.prfAddrWidth bits)) 21 | val lsType = LoadStoreType() 22 | val robIdx = UInt(config.rob.robAddressWidth bits) 23 | } 24 | 25 | /** Store buffer. 压缩式FIFO。 26 | * 27 | * @param config 28 | */ 29 | class StoreBufferPlugin(config: MyCPUConfig) extends Plugin[MemPipeline] { 30 | val depth = config.storeBufferDepth 31 | val slotType = HardType(StoreBufferSlot(config)) 32 | val queue = Vec(RegFlow(slotType()), depth) 33 | val queueNext = CombInit(queue) 34 | val queueIO = new Area { 35 | val pushPort = Stream(slotType) 36 | val popPort = Stream(slotType).setBlocked() 37 | popPort.valid := queue(0).valid 38 | popPort.payload := queue(0).payload 39 | } 40 | 41 | // Bypassing query logic 42 | val query = new Area { 43 | val addr = UWord() // Input 44 | val data = BWord().assignDontCare() // Output 45 | val be = B(0, 4 bits) // Output 46 | // 新覆盖老 47 | for (i <- 0 until depth) 48 | when( 49 | addr(2, 30 bits) === queue(i).addr(2, 30 bits) && 50 | queue(i).valid && queue(i).isStore && queue(i).isCached 51 | ) { 52 | for (j <- 0 until 4) 53 | when(queue(i).be(j)) { 54 | be(j) := True 55 | data(j * 8, 8 bits) := queue(i).data(j * 8, 8 bits) 56 | } 57 | } 58 | } 59 | 60 | override def build(pipeline: MemPipeline): Unit = pipeline plug new Area { 61 | // retire 62 | val commitStore = pipeline.globalService(classOf[CommitPlugin]).commitStore 63 | val retireFall = !queue(0).retired +: (for (i <- 1 until depth) 64 | yield queue(i - 1).retired && !queue(i).retired) 65 | for (j <- 0 until depth) { 66 | when(commitStore && retireFall(j)) { 67 | // 定位匹配,则将槽retire 68 | queue(j).retired := True 69 | queueNext(j).retired := True 70 | } 71 | } 72 | 73 | // 入队逻辑 74 | val validFall = !queue(0).valid +: (for (i <- 1 until depth) 75 | yield queue(i - 1).valid && !queue(i).valid) 76 | queueIO.pushPort.ready := !queue.last.valid 77 | for (j <- 0 until depth) { 78 | when(queueIO.pushPort.valid && validFall(j)) { 79 | // 定位匹配,则将槽入队 80 | queue(j).push(queueIO.pushPort.payload) 81 | queueNext(j).push(queueIO.pushPort.payload) 82 | } 83 | } 84 | 85 | val flush = pipeline.globalService(classOf[CommitPlugin]).regFlush 86 | when(flush) { 87 | for (i <- 0 until depth) when(!queueNext(i).payload.retired) { 88 | queue(i).valid := False 89 | queueNext(i).valid := False 90 | } 91 | } 92 | 93 | // 压缩逻辑 94 | for (i <- 0 until depth) { 95 | when(queueIO.popPort.fire) { 96 | if (i + 1 < depth) queue(i) := queueNext(i + 1) 97 | else queue(i).valid := False 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/pipeline/mem/UncachedAccess.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.mem 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import spinal.lib.fsm._ 6 | import spinal.lib.bus.amba4.axi.Axi4 7 | 8 | import NOP._ 9 | import NOP.builder._ 10 | import NOP.utils._ 11 | import NOP.pipeline._ 12 | import NOP.pipeline.core._ 13 | import NOP.constants.enum._ 14 | 15 | class UncachedAccessPlugin(config: MyCPUConfig) extends Plugin[MemPipeline] { 16 | 17 | val udBus = Axi4(config.axiConfig).setIdle() 18 | udBus.b.ready.allowOverride := True 19 | 20 | val uncachedStoreHandshake = Event.setIdle() 21 | 22 | override def build(pipeline: MemPipeline): Unit = pipeline.MEM2 plug new Area { 23 | import pipeline.MEM2._ 24 | import pipeline.signals._ 25 | val std = input(STD_SLOT) 26 | val isLDU = std.valid && !std.isStore 27 | val isSTU = std.valid && std.isStore && !std.isCached 28 | 29 | when(isSTU) { 30 | // STU直接向FSM提交写memory,要等到脏行写完 31 | uncachedStoreHandshake.valid := arbitration.notStuck 32 | arbitration.haltItself.setWhen(!uncachedStoreHandshake.ready) 33 | } 34 | 35 | val uncachedStoreFSM = new StateMachine { 36 | disableAutoStart() 37 | setEntry(stateBoot) 38 | val waitAXIWriteU = new State 39 | 40 | // Register that samples std.payload 41 | // When starting another uncached store 42 | val regSTD = RegNextWhen(std.payload, uncachedStoreHandshake.fire) 43 | uncachedStoreHandshake.setBlocked() // ready := False 44 | 45 | val awSent, wSent = RegInit(False) 46 | val bRecv = RegInit(True) 47 | awSent setWhen (udBus.aw.fire) 48 | wSent setWhen (udBus.w.fire) 49 | bRecv setWhen (udBus.b.fire) 50 | 51 | stateBoot.whenIsActive { 52 | uncachedStoreHandshake.ready := (bRecv || udBus.b.fire) 53 | when(uncachedStoreHandshake.fire) { 54 | awSent := False 55 | wSent := False 56 | bRecv := False 57 | goto(waitAXIWriteU) 58 | } 59 | } 60 | // uncached store 61 | waitAXIWriteU.whenIsActive { 62 | val aw = udBus.aw 63 | aw.valid := !awSent 64 | aw.payload.id := 2 65 | aw.payload.addr := regSTD.addr 66 | aw.payload.len := 0 67 | aw.payload.size := LoadStoreType.toAxiSize(regSTD.lsType) 68 | aw.payload.burst := B(1, 2 bits) 69 | aw.payload.lock := 0 70 | aw.payload.cache := 0 71 | if (config.axiConfig.useQos) aw.payload.qos := 0 72 | aw.payload.prot := 0 73 | val w = udBus.w 74 | w.valid := !wSent 75 | w.data := regSTD.data 76 | w.strb := regSTD.be 77 | w.last := True 78 | when((awSent || aw.fire) && (wSent || w.fire)) { goto(stateBoot) } 79 | } 80 | } 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/pipeline/privilege/ExceptionHandlerPlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.priviledge 2 | 3 | import scala.collection.mutable 4 | import spinal.core._ 5 | import spinal.lib._ 6 | import NOP._ 7 | import NOP.utils._ 8 | import NOP.builder._ 9 | import NOP.constants.LoongArch 10 | import NOP.pipeline.core._ 11 | import NOP.pipeline.fetch._ 12 | import NOP.pipeline._ 13 | 14 | class ExceptionHandlerPlugin extends Plugin[MyCPUCore] { 15 | var intHandler: InterruptHandlerPlugin = null 16 | 17 | // ! Registers 18 | // ! CRMD 19 | val CRMD_PLV = RegInit(B(0x0, 2 bits)) 20 | val CRMD_IE = RegInit(False) 21 | val CRMD_DA = RegInit(True) // Direct access 22 | val CRMD_PG = RegInit(False) // 映射地址翻译模式 23 | val CRMD_DATF = RegInit(B(0x0, 2 bits)) 24 | val CRMD_DATM = RegInit(B(0x0, 2 bits)) 25 | 26 | // ! PRMD 27 | val PRMD_PPLV = RegInit(B(0x0, 2 bits)) 28 | val PRMD_PIE = RegInit(False) 29 | 30 | // ! ECFG 31 | val ECFG_LIE_0 = RegInit(B(0x0, 10 bits)) 32 | val ECFG_LIE_10 = RegInit(B(0x0, 1 bits)) 33 | val ECFG_LIE_11 = RegInit(B(0x0, 2 bits)) 34 | 35 | // ! ESTAT 36 | val ESTAT_IS_0 = out(RegInit(B(0x0, 2 bits))) 37 | // val ESTAT_IS_2 = RegInit(B(0x0, 8 bits)) 38 | val ESTAT_IS_2 = out(RegInit(B(0x0, 8 bits))) 39 | val ESTAT_IS_11 = out(RegInit(B(0x0, 1 bits))) 40 | // val ESTAT_IS_12 = RegInit(B(0x0, 1 bits)) 41 | val ESTAT_IS_12 = out(B(0x0, 1 bits)) 42 | val ESTAT_ECODE = RegInit(B(0x0, 6 bits)) 43 | val ESTAT_ESUBCODE = RegInit(B(0x0, 9 bits)) 44 | 45 | // ! ERA 46 | val ERA_PC = RegInit(B(0x0, 32 bits)) 47 | 48 | // ! BADV 49 | val BADV_VADDR = RegInit(B(0x0, 32 bits)) 50 | 51 | // ! EENTRY 52 | val EENTRY_VA = RegInit(B(0x0, 26 bits)) 53 | val EENTRY_PC = EENTRY_VA ## B(0x0, 6 bits) 54 | 55 | // ! SAVE0~3 56 | val SAVE0_DATA = RegInit(B(0x0, 32 bits)) 57 | val SAVE1_DATA = RegInit(B(0x0, 32 bits)) 58 | val SAVE2_DATA = RegInit(B(0x0, 32 bits)) 59 | val SAVE3_DATA = RegInit(B(0x0, 32 bits)) 60 | 61 | // ! LLBCTL 62 | val LLBCTL_LLBIT = out(RegInit(False)) 63 | val LLBCTL_KLO = RegInit(False) 64 | 65 | override def setup(pipeline: MyCPUCore): Unit = { 66 | import LoongArch.CSRAddress 67 | val CSRMan = pipeline.service(classOf[CSRPlugin]) 68 | intHandler = pipeline.service(classOf[InterruptHandlerPlugin]) 69 | 70 | // !! CRMD 71 | CSRMan.rw( 72 | CSRAddress.CRMD, 73 | 0 -> CRMD_PLV, 74 | 2 -> CRMD_IE, 75 | 3 -> CRMD_DA, 76 | 4 -> CRMD_PG, 77 | 5 -> CRMD_DATF, 78 | 7 -> CRMD_DATM 79 | ) 80 | CSRMan.r0(CSRAddress.CRMD, 9, (31 downto 9).length bits) 81 | 82 | // ! PRMD 83 | CSRMan.rw( 84 | CSRAddress.PRMD, 85 | 0 -> PRMD_PPLV, 86 | 2 -> PRMD_PIE 87 | ) 88 | CSRMan.r0(CSRAddress.PRMD, 3, (31 downto 3).length bits) 89 | 90 | // ! EUEN 91 | CSRMan.r0(CSRAddress.EUEN, 0, 32 bits) 92 | 93 | // ! ECFG 94 | CSRMan.rw( 95 | CSRAddress.ECFG, 96 | 0 -> ECFG_LIE_0, 97 | 10 -> ECFG_LIE_10, 98 | 11 -> ECFG_LIE_11 99 | ) 100 | CSRMan.r0(CSRAddress.ECFG, 13, (31 downto 13).length bits) 101 | 102 | // ! ESTAT 103 | CSRMan.rw(CSRAddress.ESTAT, 0 -> ESTAT_IS_0) 104 | CSRMan.r( 105 | CSRAddress.ESTAT, 106 | 2 -> ESTAT_IS_2, 107 | 11 -> ESTAT_IS_11, 108 | 12 -> ESTAT_IS_12, 109 | 16 -> ESTAT_ECODE, 110 | 22 -> ESTAT_ESUBCODE 111 | ) 112 | CSRMan.r0(CSRAddress.ESTAT, 10, 1 bits) 113 | CSRMan.r0(CSRAddress.ESTAT, 13, 3 bits) 114 | CSRMan.r0(CSRAddress.ESTAT, 31, 1 bits) 115 | 116 | // ! ERA 117 | CSRMan.rw(CSRAddress.ERA, 0 -> ERA_PC) 118 | 119 | // ! BADV 120 | CSRMan.rw(CSRAddress.BADV, 0 -> BADV_VADDR) 121 | 122 | // ! EENTRY 123 | CSRMan.rw(CSRAddress.EENTRY, 6 -> EENTRY_VA) 124 | CSRMan.r0(CSRAddress.EENTRY, 0, 6 bits) 125 | 126 | // ! CPUID 127 | CSRMan.r0(CSRAddress.CPUID, 0, 32 bits) 128 | 129 | // ! SAVE0 ~ 3 130 | CSRMan.rw(CSRAddress.SAVE0, 0 -> SAVE0_DATA) 131 | CSRMan.rw(CSRAddress.SAVE1, 0 -> SAVE1_DATA) 132 | CSRMan.rw(CSRAddress.SAVE2, 0 -> SAVE2_DATA) 133 | CSRMan.rw(CSRAddress.SAVE3, 0 -> SAVE3_DATA) 134 | 135 | // ! LLBCTL 136 | CSRMan.rw( 137 | CSRAddress.LLBCTL, 138 | 2 -> LLBCTL_KLO 139 | ) 140 | CSRMan.r(CSRAddress.LLBCTL, 0 -> LLBCTL_LLBIT) 141 | CSRMan.w1(CSRAddress.LLBCTL, 1) { 142 | LLBCTL_LLBIT := False 143 | } 144 | 145 | } 146 | 147 | override def build(pipeline: MyCPUCore): Unit = pipeline plug new Area { 148 | import LoongArch.CSRAddress 149 | import LoongArch.ExceptionCode 150 | 151 | val jumpInterface = pipeline.fetchPipeline.service(classOf[ProgramCounterPlugin]).backendJumpInterface 152 | val excCommit = pipeline.service(classOf[CommitPlugin]) 153 | val MMUPlugin = pipeline.service(classOf[MMUPlugin]) 154 | val exceptPayload = excCommit.except 155 | val exceptErtn = excCommit.ertn 156 | val exceptEpc = excCommit.epc 157 | 158 | val takeInt = intHandler.intPending 159 | val eentry = UWord() 160 | eentry := EENTRY_PC.asUInt 161 | when(!takeInt && exceptPayload.payload.isTLBRefill) { 162 | eentry := MMUPlugin.TLBRENTRY_PA.asUInt @@ U(0x0, 6 bits) 163 | } 164 | 165 | when(exceptPayload.valid) { 166 | jumpInterface.valid := True 167 | jumpInterface.payload := eentry 168 | 169 | when(takeInt) { 170 | // Interrupt 171 | ESTAT_ECODE := B(LoongArch.ExceptionCode.INT.ecode, 6 bits) 172 | ESTAT_ESUBCODE := B(LoongArch.ExceptionCode.INT.esubcode, 9 bits) 173 | } otherwise { 174 | // Exception 175 | when(exceptPayload.isTLBRefill) { 176 | CRMD_DA := True 177 | CRMD_PG := False 178 | } 179 | // ESTAT 180 | ESTAT_ECODE := exceptPayload.payload.code 181 | ESTAT_ESUBCODE := exceptPayload.payload.subcode 182 | 183 | // BADV 184 | val BadVUpdateCases = Seq( 185 | LoongArch.ExceptionCode.TLBR, 186 | LoongArch.ExceptionCode.ADEF, 187 | LoongArch.ExceptionCode.ADEM, 188 | LoongArch.ExceptionCode.ALE, 189 | LoongArch.ExceptionCode.PIL, 190 | LoongArch.ExceptionCode.PIS, 191 | LoongArch.ExceptionCode.PIF, 192 | LoongArch.ExceptionCode.PME, 193 | LoongArch.ExceptionCode.PPI 194 | ) 195 | when( 196 | BadVUpdateCases 197 | .map({ excCase => 198 | exceptPayload.payload.code === excCase.ecode && 199 | exceptPayload.payload.subcode === excCase.esubcode 200 | }) 201 | .orR 202 | ) { 203 | BADV_VADDR := exceptPayload.payload.badVA.asBits 204 | } 205 | 206 | // TLBEHI 207 | val TLBEHIUpdateCases = Seq( 208 | LoongArch.ExceptionCode.TLBR, 209 | LoongArch.ExceptionCode.PIF, 210 | LoongArch.ExceptionCode.PPI, 211 | LoongArch.ExceptionCode.PME, 212 | LoongArch.ExceptionCode.PIS, 213 | LoongArch.ExceptionCode.PIL 214 | ) 215 | when( 216 | BadVUpdateCases 217 | .map({ excCase => 218 | exceptPayload.payload.code === excCase.ecode && 219 | exceptPayload.payload.subcode === excCase.esubcode 220 | }) 221 | .orR 222 | ) { 223 | MMUPlugin.TLBEHI_VPPN := exceptPayload.payload.badVA(31 downto 13).asBits 224 | } 225 | 226 | } 227 | 228 | PRMD_PPLV := CRMD_PLV 229 | PRMD_PIE := CRMD_IE 230 | CRMD_PLV := 0x0 231 | CRMD_IE := False 232 | 233 | ERA_PC := exceptEpc.asBits 234 | 235 | } elsewhen (excCommit.ertn) { 236 | 237 | when(LLBCTL_KLO) { 238 | LLBCTL_KLO := False 239 | } otherwise { 240 | LLBCTL_LLBIT := False 241 | } 242 | 243 | jumpInterface.valid := True 244 | jumpInterface.payload := ERA_PC.asUInt 245 | 246 | CRMD_PLV := PRMD_PPLV 247 | CRMD_IE := PRMD_PIE 248 | when(ESTAT_ECODE === B(0x3f, 6 bits)) { 249 | CRMD_DA := False 250 | CRMD_PG := True 251 | } 252 | } 253 | } 254 | 255 | } 256 | -------------------------------------------------------------------------------- /src/pipeline/privilege/InterruptHandlerPlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.priviledge 2 | 3 | import scala.collection.mutable 4 | import spinal.core._ 5 | import spinal.lib._ 6 | import NOP._ 7 | import NOP.utils._ 8 | import NOP.builder._ 9 | import NOP.constants.LoongArch 10 | import NOP.pipeline.core._ 11 | import NOP.pipeline.fetch._ 12 | import NOP.pipeline._ 13 | 14 | class InterruptHandlerPlugin(config: MyCPUConfig) extends Plugin[MyCPUCore] { 15 | var CSRMan: CSRPlugin = null 16 | var excHandler: ExceptionHandlerPlugin = null 17 | 18 | // ! Registers 19 | val TCFG_EN = RegInit(False) 20 | val TCFG_PERIODIC = RegInit(False) 21 | val TCFG_INITVAL = RegInit(U(0x0, 30 bits)) 22 | val timer_en = out(RegInit(False)) 23 | 24 | // ! TID 25 | val TID_TID = RegInit(U(0x0, 32 bits)) 26 | 27 | // ! TVAL 28 | val TVAL_TIMEVAL = out(RegInit(U(0x0, 32 bits))) 29 | 30 | val setTimerInterrupt = False // Set this to true will trigger a timer interrupt 31 | 32 | // ! Indicate if there is a pending interrupt 33 | val intPending = Bool() 34 | 35 | val ECFG = out(Bits(12 bits)) 36 | val ESTAT = out(Bits(12 bits)) 37 | 38 | override def setup(pipeline: MyCPUCore): Unit = { 39 | import LoongArch.CSRAddress 40 | CSRMan = pipeline.service(classOf[CSRPlugin]) 41 | excHandler = pipeline.service(classOf[ExceptionHandlerPlugin]) 42 | 43 | // ! Registering CSR 44 | // ! TID 45 | CSRMan.rw(CSRAddress.TID, 0 -> TID_TID) 46 | 47 | // ! TCFG 48 | CSRMan.rw( 49 | CSRAddress.TCFG, 50 | 0 -> TCFG_EN, 51 | 1 -> TCFG_PERIODIC, 52 | 2 -> TCFG_INITVAL 53 | ) 54 | CSRMan.w1(CSRAddress.TCFG, 0)({ 55 | timer_en := True 56 | }) 57 | 58 | // ! TVAL 59 | CSRMan.r( 60 | CSRAddress.TVAL, 61 | 0 -> TVAL_TIMEVAL 62 | ) 63 | 64 | // ! TICLR 65 | CSRMan.r0(CSRAddress.TICLR, 1, 31 bits) 66 | 67 | } 68 | 69 | override def build(pipeline: MyCPUCore): Unit = { 70 | import LoongArch.CSRAddress 71 | // Send timer interrupt 72 | excHandler.ESTAT_IS_11 := excHandler.ESTAT_IS_11 | setTimerInterrupt.asBits 73 | CSRMan.r0(CSRAddress.TICLR, 0, 1 bits) 74 | CSRMan.w1(CSRAddress.TICLR, 0)({ 75 | excHandler.ESTAT_IS_11 := False.asBits 76 | }) 77 | 78 | excHandler.ESTAT_IS_2 := pipeline.io.intrpt 79 | 80 | // Downsampling the clock 81 | val innerClk = Bool() // Use this to count inner clock 82 | val innerCounter = Counter(config.interrupt.innerCounterDownEvery) 83 | innerCounter.increment() 84 | innerClk := innerCounter.willOverflow 85 | 86 | // Timer Logic 87 | when(timer_en && TVAL_TIMEVAL === 0) { 88 | setTimerInterrupt.set() 89 | timer_en := TCFG_PERIODIC 90 | } 91 | 92 | when(timer_en) { 93 | when(innerClk && TVAL_TIMEVAL =/= 0) { 94 | TVAL_TIMEVAL := TVAL_TIMEVAL - 1 95 | } 96 | 97 | when(TVAL_TIMEVAL === 0) { 98 | when(TCFG_PERIODIC) { 99 | TVAL_TIMEVAL := TCFG_INITVAL @@ U(0x0, 2 bits) 100 | } otherwise { 101 | TVAL_TIMEVAL := U(0x0, 32 bits) - 1 102 | } 103 | } 104 | } 105 | 106 | CSRMan.wAny(CSRAddress.TCFG)(data => { 107 | when(data(0)) { 108 | TVAL_TIMEVAL := (data(31 downto 2) ## U(0x0, 2 bits)).asUInt 109 | } 110 | }) 111 | 112 | // INT pending logic 113 | intPending := ((excHandler.ECFG_LIE_11 ## excHandler.ECFG_LIE_0) & 114 | (excHandler.ESTAT_IS_12 ## excHandler.ESTAT_IS_11 ## excHandler.ESTAT_IS_2 ## excHandler.ESTAT_IS_0)).orR & excHandler.CRMD_IE & !excHandler.LLBCTL_LLBIT 115 | ECFG := (excHandler.ECFG_LIE_11 ## excHandler.ECFG_LIE_0) 116 | ESTAT := (excHandler.ESTAT_IS_12 ## excHandler.ESTAT_IS_11 ## excHandler.ESTAT_IS_2 ## excHandler.ESTAT_IS_0) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/pipeline/privilege/WaitHandlerPlugin.scala: -------------------------------------------------------------------------------- 1 | package NOP.pipeline.priviledge 2 | 3 | import scala.collection.mutable 4 | import spinal.core._ 5 | import spinal.lib._ 6 | import NOP._ 7 | import NOP.utils._ 8 | import NOP.builder._ 9 | import NOP.constants.LoongArch 10 | import NOP.pipeline.core._ 11 | import NOP.pipeline.fetch._ 12 | import NOP.pipeline._ 13 | 14 | class WaitHandlerPlugin extends Plugin[MyCPUCore] { 15 | override def build(pipeline: MyCPUCore): Unit = pipeline plug new Area { 16 | val inLowPowerMode = RegInit(False) 17 | inLowPowerMode setWhen pipeline.service(classOf[CommitPlugin]).doWait 18 | inLowPowerMode clearWhen pipeline.service(classOf[InterruptHandlerPlugin]).ESTAT.orR 19 | pipeline.IF1.arbitration.haltByOther setWhen inLowPowerMode 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/Axi4Rename.scala: -------------------------------------------------------------------------------- 1 | package NOP.utils 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import spinal.lib.bus.amba4.axi.Axi4 6 | 7 | object Axi4Rename { 8 | 9 | def Rename(bundle: Bundle): Unit = { 10 | bundle.elements.foreach { 11 | case (name, ref) => { 12 | // println(s"Name: $name, Ref: $ref") 13 | ref match { 14 | case b: Axi4 => { 15 | b.flattenForeach { signal => 16 | { 17 | // Replace `_` and `payload` with nothing 18 | signal.setName(signal.getName().replace("_payload_", "")) 19 | signal.setName(signal.getName().replace("_", "")) 20 | } 21 | } 22 | // print(b) 23 | } 24 | case _ => 25 | } 26 | } 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/CompressedFIFO.scala: -------------------------------------------------------------------------------- 1 | package NOP.utils 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import scala.collection.mutable 6 | 7 | import NOP.pipeline.core._ 8 | import NOP.pipeline._ 9 | import NOP.builder._ 10 | import NOP._ 11 | 12 | /** 压缩队列。 13 | * 14 | * 压缩队列按优先级从低到高: 15 | * 16 | * 1. 不压缩时自身状态转移。同步至stateNext。 17 | * 18 | * 2. 入队时覆写为初态。同步至stateNext。 19 | * 20 | * 3. 压缩时通过stateNext同步状态转移。 21 | * 22 | * 4. flush时直接清除所有valid。直接写。最高优先级,不需要stateNext同步。 23 | * 24 | * @param issConfig 25 | * @param decodeWidth 26 | * @param slotType 27 | */ 28 | abstract class CompressedFIFO[T <: IssueSlot]( 29 | issConfig: IssueConfig, 30 | val decodeWidth: Int, 31 | val slotType: HardType[T] 32 | ) extends Plugin[MyCPUCore] { 33 | val issueWidth = issConfig.issueWidth 34 | val depth = issConfig.depth 35 | 36 | val busyAddrs: Vec[UInt] 37 | var busyRsps: Vec[Bool] = null 38 | def fuMatch(uop: MicroOp): Bool 39 | 40 | val queue = out(Vec(RegFlow(slotType()), depth)) // 做槽移动 41 | val queueNext = CombInit(queue) // 标志对应槽的下一个值(不考虑压缩) 42 | 43 | val issueReq = Bool 44 | val issueFire = True // issue整体使能 45 | val queueFlush = False // 清除整个IQ 46 | 47 | val queueIO = new Area { 48 | val pushPorts = Vec(Stream(slotType), decodeWidth) 49 | } 50 | 51 | def genEnqueueLogic() = { 52 | val validFall = !queue(0).valid +: (for (i <- 1 until depth) 53 | yield queue(i - 1).valid && !queue(i).valid) 54 | // 入队逻辑 55 | for (i <- 0 until decodeWidth) { 56 | // 0口ready当且仅当depth-1是空的 57 | queueIO.pushPorts(i).ready := !queue(depth - i - 1).valid 58 | for (j <- i until depth) { 59 | when(queueIO.pushPorts(i).valid && validFall(j - i)) { 60 | // 定位匹配,则将槽入队 61 | queue(j).push(queueIO.pushPorts(i).payload) 62 | queueNext(j).push(queueIO.pushPorts(i).payload) 63 | } 64 | } 65 | } 66 | } 67 | 68 | def genCompressLogic() = { 69 | // 压缩逻辑 70 | val popCount = issueReq && issueFire 71 | require(issueWidth == 1) 72 | for (i <- 0 until depth) { 73 | when(popCount) { 74 | if (i + 1 < depth) queue(i) := queueNext(i + 1) 75 | else queue(i).valid := False 76 | } 77 | } 78 | } 79 | 80 | def genFlushLogic() = when(queueFlush) { 81 | // flush最高优先级 82 | for (i <- 0 until depth) { 83 | queue(i).valid := False 84 | } 85 | } 86 | 87 | def genIssueSelect() = { 88 | // issue选择 89 | } 90 | 91 | def genGlobalWakeup(prf: PhysRegFilePlugin, rPorts: Int) = prf.clearBusys.foreach { port => 92 | for (i <- 0 until depth) { 93 | // 远程唤醒(监听写busy广播) 94 | for (j <- 0 until rPorts) 95 | when(port.valid && port.payload === queue(i).payload.rRegs(j).payload) { 96 | queue(i).payload.rRegs(j).valid := True 97 | queueNext(i).payload.rRegs(j).valid := True 98 | } 99 | } 100 | } 101 | 102 | override def setup(pipeline: MyCPUCore): Unit = { 103 | val PRF = pipeline.service(classOf[PhysRegFilePlugin]) 104 | busyRsps = Vec(busyAddrs.map(PRF.readBusy(_))) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/utils/CompressedQueue.scala: -------------------------------------------------------------------------------- 1 | package NOP.utils 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | import scala.collection.mutable 6 | 7 | import NOP.pipeline.core._ 8 | import NOP.pipeline._ 9 | import NOP.builder._ 10 | import NOP._ 11 | 12 | /** 压缩队列。 13 | * 14 | * 压缩队列按优先级从低到高: 15 | * 16 | * 1. 不压缩时自身状态转移。同步至stateNext。 17 | * 18 | * 2. 入队时覆写为初态。同步至stateNext。 19 | * 20 | * 3. 压缩时通过stateNext同步状态转移。 21 | * 22 | * 4. flush时直接清除所有valid。直接写。最高优先级,不需要stateNext同步。 23 | * 24 | * @param issConfig 25 | * @param decodeWidth 26 | * @param slotType 27 | */ 28 | abstract class CompressedQueue[T <: IssueSlot]( 29 | issConfig: IssueConfig, 30 | val decodeWidth: Int, 31 | val slotType: HardType[T] 32 | ) extends Plugin[MyCPUCore] { 33 | val issueWidth = issConfig.issueWidth 34 | val depth = issConfig.depth 35 | 36 | val busyAddrs: Vec[UInt] // For overwritten in subclasses 37 | var busyRsps: Vec[Bool] = null // Read from PRF 38 | def fuMatch(uop: MicroOp): Bool // For overwritten in subclasses 39 | 40 | private val grantPorts = mutable.ArrayBuffer[(Seq[Bool], Vec[Bool])]() 41 | def grantPort(reqs: Seq[Bool]) = { 42 | val grants = Vec(Bool, reqs.size) 43 | grantPorts += (reqs -> grants) 44 | grants 45 | } 46 | 47 | val queue = Vec(RegFlow(slotType()), depth) // 做槽移动 48 | val queueNext = CombInit(queue) // 标志对应槽的下一个值(不考虑压缩) // 1. 不压缩时自身状态转移。同步至stateNext。 49 | 50 | val issueMask = Bits(depth bits) 51 | val issueFire = True // issue整体使能 52 | val queueFlush = False // 清除整个IQ 53 | 54 | val queueIO = new Area { 55 | val pushPorts = Vec(Stream(slotType), decodeWidth) 56 | } 57 | 58 | // 2. 入队时覆写为初态。同步至stateNext。 59 | def genEnqueueLogic() = { 60 | val validFall = !queue(0).valid +: (for (i <- 1 until depth) 61 | yield queue(i - 1).valid && !queue(i).valid) // the length of current valid depth 62 | // 入队逻辑 63 | for (i <- 0 until decodeWidth) { 64 | // 0口ready当且仅当depth-1是空的 65 | queueIO.pushPorts(i).ready := !queue(depth - i - 1).valid 66 | for (j <- i until depth) { 67 | when(queueIO.pushPorts(i).valid && validFall(j - i)) { 68 | // 定位匹配,则将槽入队 69 | queue(j).push(queueIO.pushPorts(i).payload) // Flow.push, set valid to True and payload to that 70 | queueNext(j).push(queueIO.pushPorts(i).payload) 71 | } 72 | } 73 | } 74 | } 75 | 76 | // 3. 压缩时通过stateNext同步状态转移。 77 | def genCompressLogic() = { 78 | // 压缩逻辑 79 | val popCounts = Vec(UInt(log2Up(issueWidth + 1) bits), depth) // sized depth, prefix sum of issueMask 80 | // pop等价于当前valid,下个周期不valid(被抽走了) 81 | popCounts(0) := (issueFire && issueMask(0)).asUInt.resized 82 | for (i <- 0 to depth - 2) { 83 | popCounts(i + 1) := Mux( 84 | issueFire && issueMask(i + 1), 85 | popCounts(i) + 1, 86 | popCounts(i) 87 | ) 88 | } 89 | for (i <- 0 until depth) { 90 | for (j <- 1 to issueWidth) { 91 | // 后覆盖前。考察[0, i+j-1]的清除情况。例如0被清除,则0选择1,但01都被清除,则0选择2... 92 | if (i + j < depth) when(popCounts(i + j - 1) === j)(queue(i) := queueNext(i + j)) 93 | else when(popCounts.last === j)(queue(i).valid := False) 94 | } 95 | } 96 | } 97 | 98 | // 4. flush时直接清除所有valid。直接写。最高优先级,不需要stateNext同步。 99 | def genFlushLogic() = when(queueFlush) { 100 | // flush最高优先级 101 | for (i <- 0 until depth) { 102 | queue(i).valid := False 103 | } 104 | } 105 | 106 | def genIssueSelect() = { 107 | // issue选择 108 | require(grantPorts.size <= issueWidth) 109 | 110 | // Grant only one slot for each FU 111 | if (grantPorts.nonEmpty) grantPorts(0)._2 := OHMasking.first(grantPorts(0)._1) 112 | 113 | for (i <- 1 until grantPorts.size) { 114 | val exeMask = grantPorts.map(_._2).take(i).reduceBalancedTree { (l, r) => 115 | (l.asBits | r.asBits).asBools 116 | } 117 | grantPorts(i)._2 := OHMasking.first(grantPorts(i)._1.zip(exeMask).map { case (b, m) => 118 | b && !m // Mask out already granted slots 119 | }) 120 | } 121 | issueMask := grantPorts.map(_._2.asBits).reduceBalancedTree(_ | _) // orReduce 122 | } 123 | 124 | def genGlobalWakeup(prf: PhysRegFilePlugin, rPorts: Int) = prf.clearBusys.foreach { port => 125 | for (i <- 0 until depth) { 126 | // 远程唤醒(监听写busy广播) 127 | for (j <- 0 until rPorts) 128 | when(port.valid && port.payload === queue(i).payload.rRegs(j).payload) { 129 | queue(i).payload.rRegs(j).valid := True 130 | queueNext(i).payload.rRegs(j).valid := True 131 | } 132 | } 133 | } 134 | 135 | override def setup(pipeline: MyCPUCore): Unit = { 136 | val PRF = pipeline.service(classOf[PhysRegFilePlugin]) 137 | busyRsps = Vec(busyAddrs.map(PRF.readBusy(_))) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/utils/Defines.scala: -------------------------------------------------------------------------------- 1 | package NOP.utils 2 | 3 | import spinal.core._ 4 | 5 | class UFixed(bitLen: Int) { 6 | def apply(): UInt = UInt(bitLen bits) 7 | def apply(value: Long): UInt = U(value, bitLen bits) 8 | } 9 | 10 | object UWord extends UFixed(32) 11 | object RegAddr extends UFixed(5) 12 | 13 | class BFixed(bitLen: Int) { 14 | def apply(): Bits = Bits(bitLen bits) 15 | def apply(value: Long): Bits = B(value, bitLen bits) 16 | } 17 | 18 | object BWord extends BFixed(32) 19 | -------------------------------------------------------------------------------- /src/utils/MultiPortFIFO.scala: -------------------------------------------------------------------------------- 1 | package NOP.utils 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | /** 多口同步FIFO,要求读写口等宽,但可以一次push或pop少于读写口数量的数据,但push和pop相对于口要按顺序紧密排列。 7 | * 8 | * 重排序bram实现,可用于大规模FIFO。 9 | * 10 | * @param dataType 11 | * @param depth 12 | * @param pushCount 13 | * @param popCount 14 | */ 15 | class MultiPortFIFOSyncImpl[T <: Data]( 16 | dataType: HardType[T], 17 | depth: Int, 18 | pushPorts: Int, 19 | popPorts: Int 20 | ) extends Component { 21 | // pow2保证指针循环没有问题 22 | require(depth > 0 && isPow2(depth)) 23 | val ram = new ReorderCacheRAMOutReg(dataType, depth, popPorts, pushPorts, false, true) 24 | val pushPtr = RegInit(U(0, ram.addressWidth bits)) 25 | val popPtr = RegInit(U(0, ram.addressWidth bits)) 26 | val isRisingOccupancy = RegInit(False) 27 | val isEmpty = pushPtr === popPtr && !isRisingOccupancy 28 | val isFull = pushPtr === popPtr && isRisingOccupancy 29 | val io = new Bundle { 30 | 31 | /** push的valid,pop的ready,都要遵循连续性,从头开始的第一个0就表示了停止的位置,后面的1都会被忽略。 32 | */ 33 | val push = Vec(slave(Stream(dataType)), pushPorts) 34 | val pop = Vec(master(Stream(dataType)), popPorts) 35 | } 36 | // push logic 37 | val maxPush = popPtr - pushPtr 38 | // 找到第一个非fire的 39 | val pushCount = PriorityMux(io.push.zipWithIndex.map { case (p, i) => 40 | !p.fire -> U(i) 41 | } :+ (True -> U(pushPorts))) 42 | ram.io.write.valid := io.push.map(_.valid).orR && !isFull 43 | ram.io.write.payload.address := pushPtr 44 | for (i <- 0 until pushPorts) { 45 | // 所有左侧均valid,且可以push 46 | val validTakeLeft = io.push.take(i + 1).map(_.valid).andR 47 | io.push(i).ready := isEmpty || i < maxPush 48 | ram.io.write.payload.mask(i) := io.push(i).ready && validTakeLeft 49 | ram.io.write.payload.data(i) := io.push(i).payload 50 | } 51 | pushPtr := pushPtr + pushCount 52 | 53 | // pop logic 54 | val maxPop = pushPtr - popPtr 55 | // 找到第一个非fire的 56 | val popCount = PriorityMux(io.pop.zipWithIndex.map { case (p, i) => 57 | !p.fire -> U(i) 58 | } :+ (True -> U(popPorts))) 59 | for (i <- 0 until popPorts) { 60 | // 所有左侧均ready,且可以pop 61 | val readyTakeLeft = io.pop.take(i + 1).map(_.ready).andR 62 | io.pop(i).valid := isFull || i < maxPop 63 | io.pop(i).payload := ram.io.read.rsp(i) 64 | } 65 | popPtr := popPtr + popCount 66 | ram.io.read.cmd.valid := True 67 | ram.io.read.cmd.payload := popPtr + popCount 68 | 69 | when(pushCount =/= popCount)(isRisingOccupancy := pushCount > popCount) 70 | } 71 | 72 | class MultiPortFIFOAsyncImpl[T <: Data](dataType: HardType[T], depth: Int, pushPopCount: Int) extends Component { 73 | // pow2保证指针循环没有问题 74 | require(depth > 0 && isPow2(depth)) 75 | val ram = new ReorderCacheRAMAsync(dataType, depth, pushPopCount) 76 | val pushPtr = RegInit(U(0, ram.addressWidth bits)) 77 | val popPtr = RegInit(U(0, ram.addressWidth bits)) 78 | val isRisingOccupancy = RegInit(False) 79 | val isEmpty = pushPtr === popPtr && !isRisingOccupancy 80 | val isFull = pushPtr === popPtr && isRisingOccupancy 81 | val io = new Bundle { 82 | 83 | /** push的valid,pop的ready,都要遵循连续性,从头开始的第一个0就表示了停止的位置,后面的1都会被忽略。 84 | */ 85 | val push = Vec(slave(Stream(dataType)), pushPopCount) 86 | val pop = Vec(master(Stream(dataType)), pushPopCount) 87 | } 88 | // push logic 89 | val maxPush = popPtr - pushPtr 90 | // 找到第一个非fire的 91 | val pushCount = PriorityMux(io.push.zipWithIndex.map { case (p, i) => 92 | !p.fire -> U(i) 93 | } :+ (True -> U(pushPopCount))) 94 | ram.io.write.valid := io.push.map(_.valid).orR && !isFull 95 | ram.io.write.payload.address := pushPtr 96 | for (i <- 0 until pushPopCount) { 97 | // 所有左侧均valid,且可以push 98 | val validTakeLeft = io.push.take(i + 1).map(_.valid).andR 99 | io.push(i).ready := isEmpty || i < maxPush 100 | ram.io.write.payload.mask(i) := io.push(i).ready && validTakeLeft 101 | ram.io.write.payload.data(i) := io.push(i).payload 102 | } 103 | pushPtr := pushPtr + pushCount 104 | 105 | // pop logic 106 | val maxPop = pushPtr - popPtr 107 | // 找到第一个非fire的 108 | val popCount = PriorityMux(io.pop.zipWithIndex.map { case (p, i) => 109 | !p.fire -> U(i) 110 | } :+ (True -> U(pushPopCount))) 111 | for (i <- 0 until pushPopCount) { 112 | // 所有左侧均ready,且可以pop 113 | val readyTakeLeft = io.pop.take(i + 1).map(_.ready).andR 114 | io.pop(i).valid := isFull || i < maxPop 115 | io.pop(i).payload := ram.io.read.data(i) 116 | } 117 | popPtr := popPtr + popCount 118 | ram.io.read.address := popPtr 119 | 120 | when(pushCount =/= popCount)(isRisingOccupancy := pushCount > popCount) 121 | } 122 | -------------------------------------------------------------------------------- /src/utils/MultiPortFIFOVec.scala: -------------------------------------------------------------------------------- 1 | package NOP.utils 2 | 3 | import spinal.core._ 4 | import spinal.lib._ 5 | 6 | /** 多口同步FIFO,要求读写口等宽,但一次可以读写更少的量,不过必须在顺序的口上。 7 | * 8 | * 寄存器Vec实现,仅可用于小规模FIFO。好处是延迟仅为1 cycle,很方便插进流水线。 9 | * 10 | * 注意同步填充暂不支持。也就是说如果满了,即使这个周期要pop,也不能push,对时序好一些。 11 | * 12 | * @param dataType 13 | * @param depth 14 | * @param pushCount 15 | * @param popCount 16 | */ 17 | class MultiPortFIFOVec[T <: Data]( 18 | dataType: HardType[T], 19 | depth: Int, 20 | pushPorts: Int, 21 | popPorts: Int, 22 | dataInit: Int => T = null, 23 | pushInit: Int = 0, 24 | popInit: Int = 0, 25 | initFull: Boolean = false 26 | ) extends Component { 27 | // pow2保证指针循环没有问题 28 | require(depth > 0 && isPow2(depth)) 29 | val addressWidth = log2Up(depth) 30 | val dataVec = 31 | if (dataInit != null) Vec.tabulate(depth)({ i => RegInit(dataInit(i)) }) 32 | else Reg(Vec(dataType, depth)) 33 | def dataRead(addr: UInt) = dataVec(addr) 34 | def dataWrite(addr: UInt, value: T) = dataVec(addr) := value 35 | val pushPtr = RegInit(U(pushInit, addressWidth bits)) 36 | val popPtr = RegInit(U(popInit, addressWidth bits)) 37 | val pushEquals = (0 until depth).map { i => pushPtr === i } 38 | val popEquals = (0 until depth).map { i => popPtr === i } 39 | def dataRead(base: UInt, offset: Int) = { 40 | val result = 41 | dataVec.dataType().setCompositeName(dataVec, "readPort", weak = true).assignDontCare() 42 | dataVec.zipWithIndex.foreach { case (d, i) => 43 | when(popEquals((i - offset + depth) % depth))(result := dataVec(i)) 44 | } 45 | result 46 | } 47 | def dataWrite(base: UInt, offset: Int, value: T) = dataVec.zipWithIndex.foreach { case (d, i) => 48 | when(pushEquals((i - offset + depth) % depth))(dataVec(i) := value) 49 | } 50 | val isRisingOccupancy = RegInit(Bool(initFull)) 51 | val isEmpty = pushPtr === popPtr && !isRisingOccupancy 52 | val isFull = pushPtr === popPtr && isRisingOccupancy 53 | val io = new Bundle { 54 | 55 | /** push的valid,pop的ready,都要遵循连续性,从头开始的第一个0就表示了停止的位置,后面的1都会被忽略。 56 | */ 57 | val push = Vec(slave(Stream(dataType)), pushPorts) 58 | val pop = Vec(master(Stream(dataType)), popPorts) 59 | } 60 | // push logic 61 | val maxPush = popPtr - pushPtr 62 | // 找到第一个非fire的 63 | val pushCount = PriorityMux(io.push.zipWithIndex.map { case (p, i) => 64 | !p.fire -> U(i) 65 | } :+ (True -> U(pushPorts))) 66 | for (i <- 0 until pushPorts) { 67 | // 所有左侧均valid,且可以push 68 | val validTakeLeft = io.push.take(i + 1).map(_.valid).andR 69 | io.push(i).ready := isEmpty || i < maxPush 70 | when(io.push(i).ready && validTakeLeft)(dataWrite(pushPtr, i, io.push(i).payload)) 71 | } 72 | pushPtr := pushPtr + pushCount 73 | 74 | // pop logic 75 | val maxPop = pushPtr - popPtr 76 | // 找到第一个非fire的 77 | val popCount = PriorityMux(io.pop.zipWithIndex.map { case (p, i) => 78 | !p.fire -> U(i) 79 | } :+ (True -> U(popPorts))) 80 | for (i <- 0 until popPorts) { 81 | // 所有左侧均ready,且可以pop 82 | val readyTakeLeft = io.pop.take(i + 1).map(_.ready).andR 83 | io.pop(i).valid := isFull || i < maxPop 84 | io.pop(i).payload := dataRead(popPtr, i) 85 | } 86 | popPtr := popPtr + popCount 87 | 88 | when(pushCount =/= popCount)(isRisingOccupancy := pushCount > popCount) 89 | } 90 | 91 | class MultiPortFIFOVecOutReg[T <: Data]( 92 | dataType: HardType[T], 93 | depth: Int, 94 | pushPorts: Int, 95 | popPorts: Int, 96 | dataInit: Int => T = null, 97 | pushInit: Int = 0, 98 | popInit: Int = 0, 99 | initFull: Boolean = false 100 | ) extends Component { 101 | // pow2保证指针循环没有问题 102 | require(depth > 0 && isPow2(depth)) 103 | val addressWidth = log2Up(depth) 104 | val dataVec = 105 | if (dataInit != null) Vec.tabulate(depth)({ i => RegInit(dataInit(i)) }) 106 | else Reg(Vec(dataType, depth)) 107 | val pushPtr = RegInit(U(pushInit, addressWidth bits)) 108 | val popPtr = RegInit(U(popInit, addressWidth bits)) 109 | val pushEquals = (0 until depth).map { i => pushPtr === i } 110 | val popEquals = (0 until depth).map { i => popPtr === i } 111 | val isRisingOccupancy = RegInit(Bool(initFull)) 112 | val isEmpty = pushPtr === popPtr && !isRisingOccupancy 113 | val isFull = pushPtr === popPtr && isRisingOccupancy 114 | val io = new Bundle { 115 | 116 | /** push的valid,pop的ready,都要遵循连续性,从头开始的第一个0就表示了停止的位置,后面的1都会被忽略。 117 | */ 118 | val push = Vec(slave(Stream(dataType)), pushPorts) 119 | val pop = Vec(master(Stream(dataType)), popPorts) 120 | } 121 | 122 | // pop logic 123 | val maxPop = pushPtr - popPtr 124 | // 找到第一个非fire的 125 | val popCount = PriorityMux(io.pop.zipWithIndex.map { case (p, i) => 126 | !p.fire -> U(i) 127 | } :+ (True -> U(popPorts))) 128 | for (i <- 0 until popPorts) { 129 | // 所有左侧均ready,且可以pop 130 | val readyTakeLeft = io.pop.take(i + 1).map(_.ready).andR 131 | io.pop(i).valid := isFull || i < maxPop 132 | io.pop(i).payload.setAsReg() 133 | io.pop(i).payload := dataVec(popPtr + popCount + i) 134 | } 135 | popPtr := popPtr + popCount 136 | 137 | // push logic 138 | val maxPush = popPtr - pushPtr 139 | // 找到第一个非fire的 140 | val pushCount = PriorityMux(io.push.zipWithIndex.map { case (p, i) => 141 | !p.fire -> U(i) 142 | } :+ (True -> U(pushPorts))) 143 | for (i <- 0 until pushPorts) { 144 | // 所有左侧均valid,且可以push 145 | val validTakeLeft = io.push.take(i + 1).map(_.valid).andR 146 | io.push(i).ready := isEmpty || i < maxPush 147 | when(io.push(i).ready && validTakeLeft) { 148 | dataVec(pushPtr + i) := io.push(i).payload 149 | for (j <- i until popPorts) when(pushPtr + i === popPtr + popCount + j) { 150 | io.pop(j).payload := io.push(i).payload 151 | } 152 | } 153 | } 154 | pushPtr := pushPtr + pushCount 155 | 156 | when(pushCount =/= popCount)(isRisingOccupancy := pushCount > popCount) 157 | } 158 | -------------------------------------------------------------------------------- /src/utils/ReorderCacheRAM.scala: -------------------------------------------------------------------------------- 1 | package NOP.utils 2 | 3 | import NOP.blackbox.mem._ 4 | 5 | import spinal.core._ 6 | import spinal.lib._ 7 | import scala.math 8 | 9 | final case class RAMWriteCmdWithMask[T <: Data]( 10 | wordType: HardType[T], 11 | addressWidth: Int, 12 | maskWidth: Int 13 | ) extends Bundle { 14 | val address = UInt(addressWidth bits) 15 | val data = wordType() 16 | val mask = Bits(maskWidth bits) 17 | } 18 | 19 | /** 读写任意连续n个数据,地址可以不对齐,不存在冲突。写可以对每个数据进行enable。 20 | * 21 | * @param wordCount 22 | * Mem中总共的数据数量 23 | * @param portCount 24 | * 一次读写的数据数量 25 | */ 26 | class ReorderCacheRAM[T <: Data]( 27 | dataType: HardType[T], 28 | wordCount: Int, 29 | portCount: Int, 30 | outputReg: Boolean = true, 31 | writeFirst: Boolean = false 32 | ) extends Component { 33 | val addressWidth = log2Up(wordCount) 34 | assert(isPow2(portCount)) 35 | val offWidth = log2Up(portCount) 36 | assert(wordCount % portCount == 0) 37 | val wordsPerBank = wordCount / portCount 38 | require(!(writeFirst && outputReg)) 39 | val rams = 40 | if (writeFirst) Seq.fill(portCount)(new SDPRAMAsyncToSyncWriteFirst(dataType, wordsPerBank)) 41 | else Seq.fill(portCount)(new SDPRAM(dataType, wordsPerBank, outputReg)) 42 | rams.zipWithIndex.foreach { case (ram, idx) => 43 | ram.setWeakName("ram" + idx) 44 | OwnableRef.proposal(ram, this) 45 | } 46 | val rPorts = Vec(rams.map(_.io.read)) 47 | val wPorts = Vec(rams.map(_.io.write)) 48 | val io = new Bundle { 49 | val read = slave(MemReadPort(Vec(dataType, portCount), addressWidth)) 50 | val write = in(Flow(RAMWriteCmdWithMask(Vec(dataType, portCount), addressWidth, portCount))) 51 | } 52 | val readLogic = new Area { 53 | val addrHi = io.read.cmd.payload((addressWidth - 1) downto offWidth) 54 | val offset = io.read.cmd.payload(0, offWidth bits) 55 | rPorts.zipWithIndex.map { case (p, idx) => 56 | p.cmd.valid := io.read.cmd.valid 57 | // 例如4路,offset=2,则23均为当前地址,01为下一行 58 | p.cmd.payload := addrHi + (idx < offset).asUInt 59 | } 60 | // 延迟address,输出重排序 61 | val regAddr = Delay(offset, if (outputReg) 2 else 1, io.read.cmd.valid) 62 | io.read.rsp.zipWithIndex.map { case (rsp, idx) => 63 | rsp := rPorts(regAddr + idx).rsp 64 | } 65 | } 66 | val writeLogic = new Area { 67 | // 可惜splitAt输出是bits tuple 68 | val addrHi = io.write.payload.address((addressWidth - 1) downto offWidth) 69 | val offset = io.write.payload.address(0, offWidth bits) 70 | wPorts.zipWithIndex.map { case (p, idx) => 71 | p.valid := io.write.valid && io.write.payload.mask(idx - offset) 72 | // 同read 73 | p.payload.address := addrHi + (idx < offset).asUInt 74 | // 例如4路,offset=2,则2301 RAM分别抓0123口 75 | p.payload.data := io.write.payload.data(idx - offset) 76 | } 77 | } 78 | } 79 | 80 | class ReorderCacheRAMAsync[T <: Data]( 81 | dataType: HardType[T], 82 | wordCount: Int, 83 | portCount: Int 84 | ) extends Component { 85 | val addressWidth = log2Up(wordCount) 86 | assert(isPow2(portCount)) 87 | val offWidth = log2Up(portCount) 88 | assert(wordCount % portCount == 0) 89 | val wordsPerBank = wordCount / portCount 90 | val rams = Seq.fill(portCount)(new SDPRAMAsync(dataType, wordsPerBank)) 91 | rams.zipWithIndex.foreach { case (ram, idx) => 92 | ram.setWeakName("ram" + idx) 93 | OwnableRef.proposal(ram, this) 94 | } 95 | val rPorts = Vec(rams.map(_.io.read)) 96 | val wPorts = Vec(rams.map(_.io.write)) 97 | val io = new Bundle { 98 | val read = slave(MemReadPortAsync(Vec(dataType, portCount), addressWidth)) 99 | val write = in(Flow(RAMWriteCmdWithMask(Vec(dataType, portCount), addressWidth, portCount))) 100 | } 101 | val readLogic = new Area { 102 | val addrHi = io.read.address((addressWidth - 1) downto offWidth) 103 | val offset = io.read.address(0, offWidth bits) 104 | rPorts.zipWithIndex.map { case (p, idx) => 105 | // 例如4路,offset=2,则23均为当前地址,01为下一行 106 | p.address := addrHi + (idx < offset).asUInt 107 | } 108 | // 输出重排序 109 | io.read.data.zipWithIndex.map { case (rsp, idx) => 110 | rsp := rPorts(offset + idx).data 111 | } 112 | } 113 | val writeLogic = new Area { 114 | // 可惜splitAt输出是bits tuple 115 | val addrHi = io.write.payload.address((addressWidth - 1) downto offWidth) 116 | val offset = io.write.payload.address(0, offWidth bits) 117 | wPorts.zipWithIndex.map { case (p, idx) => 118 | p.valid := io.write.valid && io.write.payload.mask(idx - offset) 119 | // 同read 120 | p.payload.address := addrHi + (idx < offset).asUInt 121 | // 例如4路,offset=2,则2301 RAM分别抓0123口 122 | p.payload.data := io.write.payload.data(idx - offset) 123 | } 124 | } 125 | } 126 | 127 | class ReorderCacheRAMOutReg[T <: Data]( 128 | dataType: HardType[T], 129 | wordCount: Int, 130 | rPortCount: Int, 131 | wPortCount: Int, 132 | outputReg: Boolean = true, 133 | writeFirst: Boolean = false 134 | ) extends Component { 135 | val addressWidth = log2Up(wordCount) 136 | val offWidth = log2Up(math.max(rPortCount, wPortCount)) 137 | val bankCount = 1 << offWidth 138 | assert(wordCount % bankCount == 0) 139 | val wordsPerBank = wordCount / bankCount 140 | require((writeFirst && !outputReg) || (!writeFirst && outputReg)) 141 | val rams = 142 | if (writeFirst) 143 | Seq.fill(bankCount)(new SDPRAMAsyncToSyncWriteFirst(dataType, wordsPerBank, false)) 144 | else Seq.fill(bankCount)(new SDPRAM(dataType, wordsPerBank, false)) 145 | rams.zipWithIndex.foreach { case (ram, idx) => 146 | ram.setWeakName("ram" + idx) 147 | OwnableRef.proposal(ram, this) 148 | } 149 | val rPorts = Vec(rams.map(_.io.read)) 150 | val wPorts = Vec(rams.map(_.io.write)) 151 | val io = new Bundle { 152 | val read = slave(MemReadPort(Vec(dataType, rPortCount), addressWidth)) 153 | val write = in(Flow(RAMWriteCmdWithMask(Vec(dataType, wPortCount), addressWidth, wPortCount))) 154 | } 155 | io.read.rsp.setAsReg() 156 | val readLogic = new Area { 157 | val addrHi = io.read.cmd.payload((addressWidth - 1) downto offWidth) 158 | val offset = io.read.cmd.payload(0, offWidth bits) 159 | rPorts.zipWithIndex.map { case (p, idx) => 160 | p.cmd.valid := io.read.cmd.valid 161 | // 例如4路,offset=2,则23均为当前地址,01为下一行 162 | p.cmd.payload := addrHi + (idx < offset).asUInt 163 | } 164 | // 延迟address,输出重排序 165 | val regAddr = Delay(offset, if (outputReg) 1 else 0, io.read.cmd.valid) 166 | io.read.rsp.zipWithIndex.map { case (rsp, idx) => 167 | rsp := rPorts(regAddr + idx).rsp 168 | } 169 | } 170 | val writeLogic = new Area { 171 | // 可惜splitAt输出是bits tuple 172 | val addrHi = io.write.payload.address((addressWidth - 1) downto offWidth) 173 | val offset = io.write.payload.address(0, offWidth bits) 174 | wPorts.zipWithIndex.map { case (p, idx) => 175 | val writeMask = False 176 | val writeData = dataType().assignDontCare() 177 | for (i <- 0 until wPortCount) when(offset === (idx - i + bankCount) % bankCount) { 178 | writeMask := io.write.payload.mask(i) 179 | writeData := io.write.payload.data(i) 180 | } 181 | p.valid := io.write.valid && writeMask 182 | // 同read 183 | p.payload.address := addrHi + (idx < offset).asUInt 184 | // 例如4路,offset=2,则2301 RAM分别抓0123口 (idx-offset) 185 | p.payload.data := writeData 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /system_test_scratch.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM vivado:2019.2 2 | 3 | COPY ./linux_run.tar / 4 | RUN cd / && sudo tar -xf /linux_run.tar 5 | RUN sudo rm /linux_run.tar 6 | RUN sudo chmod -R 777 /linux_run 7 | 8 | # as system_test_scratch --------------------------------------------------------------------------------