From 502803c37fd1ba462686c3138f9ee12575412851 Mon Sep 17 00:00:00 2001 From: abnerhexu <20591243+abnerhexu@users.noreply.github.com> Date: Fri, 26 Jun 2026 08:20:25 +0000 Subject: [PATCH] Initial Chisel core implementation --- .gitignore | 25 + ARCHITECTURE.md | 466 ++++++++++++++++++ build.sbt | 22 + project/build.properties | 1 + sim/README.md | 98 ++++ sim/scripts/run_tests.sh | 61 +++ sim/verilator/Makefile | 43 ++ sim/verilator/memory.cpp | 111 +++++ sim/verilator/memory.h | 40 ++ sim/verilator/testbench.cpp | 101 ++++ src/main/scala/Core.scala | 173 +++++++ src/main/scala/commit/CommitStage.scala | 18 + src/main/scala/common/Bundles.scala | 76 +++ src/main/scala/common/Consts.scala | 35 ++ src/main/scala/common/Parameters.scala | 28 ++ src/main/scala/csr/CSRFile.scala | 99 ++++ src/main/scala/csr/PrivilegeControl.scala | 21 + src/main/scala/decode/Decoder.scala | 150 ++++++ src/main/scala/decode/IDStage.scala | 20 + src/main/scala/execute/ALU.scala | 36 ++ src/main/scala/execute/BranchUnit.scala | 21 + src/main/scala/execute/Divider.scala | 14 + src/main/scala/execute/ExecStage.scala | 29 ++ src/main/scala/execute/FPU.scala | 16 + src/main/scala/execute/Multiplier.scala | 11 + src/main/scala/frontend/BTB.scala | 32 ++ src/main/scala/frontend/BranchPredictor.scala | 38 ++ src/main/scala/frontend/Frontend.scala | 43 ++ src/main/scala/frontend/ICache.scala | 23 + src/main/scala/frontend/ITLB.scala | 16 + src/main/scala/frontend/RAS.scala | 31 ++ src/main/scala/issue/IssueQueue.scala | 24 + src/main/scala/issue/IssueStage.scala | 24 + src/main/scala/issue/ReservationStation.scala | 24 + src/main/scala/memory/DCache.scala | 20 + src/main/scala/memory/DTLB.scala | 15 + src/main/scala/memory/LSU.scala | 30 ++ src/main/scala/memory/LoadQueue.scala | 23 + src/main/scala/memory/MMU.scala | 15 + src/main/scala/memory/MemStage.scala | 26 + src/main/scala/memory/StoreQueue.scala | 24 + src/main/scala/regfile/PhysicalRegFile.scala | 23 + src/main/scala/rename/FreeList.scala | 26 + src/main/scala/rename/ROB.scala | 71 +++ src/main/scala/rename/RenameStage.scala | 49 ++ src/main/scala/rename/RenameTable.scala | 32 ++ src/main/scala/writeback/WriteBackStage.scala | 18 + 47 files changed, 2342 insertions(+) create mode 100644 .gitignore create mode 100644 ARCHITECTURE.md create mode 100644 build.sbt create mode 100644 project/build.properties create mode 100644 sim/README.md create mode 100755 sim/scripts/run_tests.sh create mode 100644 sim/verilator/Makefile create mode 100644 sim/verilator/memory.cpp create mode 100644 sim/verilator/memory.h create mode 100644 sim/verilator/testbench.cpp create mode 100644 src/main/scala/Core.scala create mode 100644 src/main/scala/commit/CommitStage.scala create mode 100644 src/main/scala/common/Bundles.scala create mode 100644 src/main/scala/common/Consts.scala create mode 100644 src/main/scala/common/Parameters.scala create mode 100644 src/main/scala/csr/CSRFile.scala create mode 100644 src/main/scala/csr/PrivilegeControl.scala create mode 100644 src/main/scala/decode/Decoder.scala create mode 100644 src/main/scala/decode/IDStage.scala create mode 100644 src/main/scala/execute/ALU.scala create mode 100644 src/main/scala/execute/BranchUnit.scala create mode 100644 src/main/scala/execute/Divider.scala create mode 100644 src/main/scala/execute/ExecStage.scala create mode 100644 src/main/scala/execute/FPU.scala create mode 100644 src/main/scala/execute/Multiplier.scala create mode 100644 src/main/scala/frontend/BTB.scala create mode 100644 src/main/scala/frontend/BranchPredictor.scala create mode 100644 src/main/scala/frontend/Frontend.scala create mode 100644 src/main/scala/frontend/ICache.scala create mode 100644 src/main/scala/frontend/ITLB.scala create mode 100644 src/main/scala/frontend/RAS.scala create mode 100644 src/main/scala/issue/IssueQueue.scala create mode 100644 src/main/scala/issue/IssueStage.scala create mode 100644 src/main/scala/issue/ReservationStation.scala create mode 100644 src/main/scala/memory/DCache.scala create mode 100644 src/main/scala/memory/DTLB.scala create mode 100644 src/main/scala/memory/LSU.scala create mode 100644 src/main/scala/memory/LoadQueue.scala create mode 100644 src/main/scala/memory/MMU.scala create mode 100644 src/main/scala/memory/MemStage.scala create mode 100644 src/main/scala/memory/StoreQueue.scala create mode 100644 src/main/scala/regfile/PhysicalRegFile.scala create mode 100644 src/main/scala/rename/FreeList.scala create mode 100644 src/main/scala/rename/ROB.scala create mode 100644 src/main/scala/rename/RenameStage.scala create mode 100644 src/main/scala/rename/RenameTable.scala create mode 100644 src/main/scala/writeback/WriteBackStage.scala diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e8e86ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# SBT specific +target/ +project/target/ +project/project/ + +# IDE +.idea/ +.vscode/ +.bsp/ +.metals/ +.bloop/ +metals.sbt + +# Scala +*.class +*.log + +# Generated files +generated/ +sim/verilator/obj_dir/ +sim/scripts/test_results.txt +riscv-tests/ + +# toolchain +toolchain/ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..eb84866 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,466 @@ +# RISC-V RV64G 双发射乱序处理器架构设计 + +## 目标规格 +- **ISA**: RV64IMAFD (RV64G without C extension) +- **Issue Width**: 双发射(每周期2条指令) +- **Physical Registers**: 64个物理寄存器 +- **Branch Predictor**: Gshare +- **Memory**: Sv39分页,分离的L1 ICache/DCache +- **Pipeline**: 11级流水线,乱序执行 + +--- + +## 1. 流水线架构 + +### 流水线阶段 + +``` +┌─────┐ ┌─────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌────┐ +│ IF1 │ → │ IF2 │ → │ ID │ → │ RN │ → │ DP │ → │ IS │ → │ EX1 │ → │ EX2 │ → │ MEM │ → │ WB │ → │ CM │ +└─────┘ └─────┘ └────┘ └────┘ └────┘ └────┘ └─────┘ └─────┘ └─────┘ └────┘ + TLB ICache Decode Rename Dispatch Issue Execute Execute DCache Write Commit + BPU 2-way Access Back ROB +``` + +### 各阶段功能 + +1. **IF1 (Instruction Fetch 1)** + - PC生成与选择 + - ITLB查询(虚拟地址 → 物理地址) + - Gshare分支预测 + - 取两条指令(双发射) + +2. **IF2 (Instruction Fetch 2)** + - ICache访问(64B缓存行) + - 指令对齐与缓冲 + +3. **ID (Instruction Decode)** + - 双路译码器(每周期译码2条指令) + - 指令类型识别 + - 立即数生成 + - 依赖分析(RAW检测) + +4. **RN (Rename)** + - 寄存器重命名(32逻辑寄存器 → 64物理寄存器) + - 空闲列表(Free List)管理 + - 重命名表(Rename Map Table)更新 + - ROB分配 + +5. **DP (Dispatch)** + - 分发到保留站(Reservation Station) + - 根据指令类型选择功能单元队列 + +6. **IS (Issue)** + - 从保留站唤醒就绪指令 + - 乱序发射到执行单元 + - 读取物理寄存器文件 + +7. **EX1 (Execute 1)** + - ALU运算 + - 分支计算 + - 地址生成(load/store) + - 浮点运算第一阶段 + +8. **EX2 (Execute 2)** + - 多周期运算完成 + - 浮点运算完成 + - 分支结果检查 + +9. **MEM (Memory)** + - DTLB查询 + - DCache访问 + - Load/Store执行 + - Store Buffer管理 + +10. **WB (Write Back)** + - 写回物理寄存器 + - 唤醒依赖指令 + - 更新ROB状态 + +11. **CM (Commit)** + - ROB按序提交 + - 架构状态更新 + - 异常/中断处理 + - 物理寄存器释放 + +--- + +## 2. 核心部件设计 + +### 2.1 前端 (Front-End) + +#### Gshare 分支预测器 + +``` +├── Global History Register (GHR): 12-bit +├── Pattern History Table (PHT): 4096 entries × 2-bit saturating counter +├── Branch Target Buffer (BTB): 512 entries +│ ├── Tag: 20-bit +│ ├── Target Address: 64-bit +│ └── Valid bit +└── Return Address Stack (RAS): 16 entries +``` + +**Gshare索引计算**: `index = (PC[13:2] XOR GHR[11:0])` + +要求:Branch Target Buffer大小可配置;Return Address Stack大小可配置 + +#### ICache +``` +├── Capacity: 32 KB +├── Associativity: 4-way set-associative +├── Line Size: 64 bytes +├── Sets: 128 +└── Replacement: Pseudo-LRU +``` + +要求:Capacity大小可配置;Associativity大小可配置 + +#### ITLB +``` +├── Entries: 32 (Fully associative) +├── Page Size: 4KB (Sv39) +├── Replacement: LRU +└── Support: Mega pages (2MB), Giga pages (1GB) +``` + +要求:Entries大小可配置 + +### 2.2 乱序执行核心 + +#### 重命名逻辑 +``` +Rename Map Table (RMT) +├── Arch Regs: 32 (x0-x31) +├── Phys Regs: 64 (p0-p63) +└── Mapping: arch_reg → phys_reg + +Free List +├── Available physical registers +└── FIFO allocation + +Committed Map Table (CMT) +└── For precise exception recovery +``` + +#### Reorder Buffer (ROB) +``` +├── Entries: 64 +├── Fields per entry: +│ ├── Valid +│ ├── PC +│ ├── Instruction Type +│ ├── Destination Physical Register +│ ├── Old Physical Register (for freeing) +│ ├── Exception Info +│ ├── Completed +│ └── Branch Mispredict +├── Head Pointer (commit) +└── Tail Pointer (allocate) +``` + +要求:Entries可配置 + +#### Reservation Stations +``` +Integer RS +├── Entries: 16 +└── ALU, Branch, Integer Multiply/Divide + +Load/Store RS +├── Entries: 12 +└── Address generation, Memory ops + +FP RS +├── Entries: 16 +└── FP Add, FP Multiply, FP Divide +``` + +#### Physical Register File +``` +Integer PRF +├── Registers: 64 × 64-bit +└── Read Ports: 4 (dual-issue × 2 operands) +└── Write Ports: 2 (dual-issue) + +FP PRF +├── Registers: 64 × 64-bit +└── Read Ports: 4 +└── Write Ports: 2 +``` + +### 2.3 执行单元 + +``` +├── ALU0: Integer ALU (ADD, SUB, AND, OR, XOR, SLT, etc.) +├── ALU1: Integer ALU +├── Branch Unit: Branch comparison +├── AGU: Address Generation Unit (Load/Store) +├── MDU: Multiply/Divide Unit (3-cycle latency for MUL, iterative for DIV) +├── FPU0: FP Add/Sub (4-cycle latency) +├── FPU1: FP Multiply (5-cycle latency) +└── FPU2: FP Divide/Sqrt (iterative, variable latency) +``` + +### 2.4 后端 (Memory Subsystem) + +#### DCache +``` +├── Capacity: 32 KB +├── Associativity: 8-way set-associative +├── Line Size: 64 bytes +├── Sets: 64 +├── Replacement: Pseudo-LRU +├── Write Policy: Write-back +└── MSHR: 4 entries (Miss Status Holding Registers) +``` + +要求:Capacity大小可配置;Associativity大小可配置 + +#### DTLB +``` +├── Entries: 32 (Fully associative) +├── Page Size: 4KB (Sv39) +├── Replacement: LRU +└── Support: Mega pages (2MB), Giga pages (1GB) +``` + +要求:Entries大小可配置 + +#### MMU (Sv39) +``` +├── Virtual Address: 39-bit +├── Physical Address: 56-bit (RISC-V spec allows up to 56) +├── Page Table Walker: +│ ├── 3-level page table +│ ├── Hardware page table walk +│ └── PTE cache (8 entries per level) +└── CSR Support: + ├── satp (Supervisor Address Translation and Protection) + ├── sstatus, sie, sip, stvec + └── sepc, scause, stval, sscratch +``` + +#### Load/Store Queue +``` +Load Queue (LQ) +├── Entries: 16 +└── Track in-flight loads + +Store Queue (SQ) +├── Entries: 16 +├── Store-to-Load forwarding +└── Drain on fence/commit +``` + +要求:Entries大小可配置 + +### 2.5 提交阶段 + +``` +Commit Logic +├── Commit Width: 2 instructions per cycle +├── In-order commit from ROB head +├── Exception handling: +│ ├── Flush pipeline +│ ├── Restore architectural state from CMT +│ └── Free speculative physical registers +└── Branch mispredict recovery: + ├── Flush younger instructions + ├── Restore RMT from ROB checkpoint + └── Redirect fetch +``` + +--- + +## 3. 数据通路 + +### 3.1 指令流 +``` +PC → ITLB → ICache → Instruction Buffer → Decoder (×2) → Rename → +Dispatch → Reservation Stations → Issue → Execute → Write Back → ROB → Commit +``` + +### 3.2 数据流 +``` +Read PRF → Execute → Write Result → Broadcast (Bypass) → Write PRF → Update ROB + └→ Wakeup dependent instructions +``` + +### 3.3 控制流 +``` +Branch Predictor → Speculative Fetch → Execute Branch → Compare → +[Match: Continue] / [Mispredict: Flush + Redirect] +``` + +--- + +## 4. 关键设计决策 + +### 4.1 双发射策略 +- **静态配对**: 在ID阶段检查指令间依赖 +- **限制**: 不允许同时发射两条分支指令、两条访存指令 +- **对齐**: 指令对必须来自连续的PC + +### 4.2 物理寄存器分配 +- **64个物理寄存器**: 32个用于映射当前架构状态,32个用于投机执行 +- **回收时机**: 指令提交时,释放旧的物理寄存器 + +### 4.3 分支预测恢复 +- **检查点机制**: 每条分支在RN阶段创建RMT快照 +- **恢复**: 分支错误预测时,恢复到该分支的RMT快照 + +### 4.4 内存一致性 +- **顺序模型**: RISC-V Weak Memory Order (RVWMO) +- **Load乱序**: 可以越过Store执行(需要地址消歧) +- **Store Buffer**: Store指令提交后写入,按序释放到Cache + +### 4.5 异常处理 +- **精确异常**: 通过ROB实现 +- **异常类型**: + - Page Fault (ITLB/DTLB miss) + - Illegal Instruction + - Misaligned Access + - Breakpoint + - System Call + +--- + +## 5. 性能参数估算 + +| 指标 | 目标值 | +|------|--------| +| IPC (理想) | 1.8-2.0 | +| IPC (实际) | 1.2-1.5 (考虑分支错误、Cache miss) | +| 分支预测准确率 | >90% (Gshare) | +| ICache命中率 | >95% | +| DCache命中率 | >90% | +| TLB命中率 | >98% | +| 频率目标 | 100-200 MHz (FPGA) | + +--- + +## 6. Linux启动需求 + +要成功启动Linux,CPU必须支持: + +### 6.1 特权级 +- **M-mode** (Machine): 最高特权级,处理异常/中断 +- **S-mode** (Supervisor): Linux内核运行在此 +- **U-mode** (User): 用户程序 + +### 6.2 必需的CSR +``` +Machine Mode: +- mstatus, misa, medeleg, mideleg +- mtvec, mepc, mcause, mtval +- mip, mie, mtime, mtimecmp + +Supervisor Mode: +- sstatus, stvec, sepc, scause, stval +- satp (MMU配置) +- sip, sie + +Counters: +- cycle, time, instret +``` + +### 6.3 中断/异常 +- Timer interrupt (CLINT) +- External interrupt (PLIC) +- Software interrupt +- 所有RISC-V异常类型 + +### 6.4 原子指令 (A扩展) +- LR/SC (Load-Reserved/Store-Conditional) +- AMO (Atomic Memory Operations): AMOSWAP, AMOADD, AMOXOR, AMOAND, AMOOR, AMOMIN, AMOMAX + +### 6.5 外设接口 +- UART (串口输出) +- CLINT (核心本地中断器) +- PLIC (平台级中断控制器) + +--- + +## 7. 验证策略 + +### 7.1 单元测试 +- 每个模块的功能验证 +- Chisel Unit Tests + +### 7.2 ISA测试 +- riscv-tests (官方ISA测试套件) +- rv64ui, rv64um, rv64ua, rv64uf, rv64ud + +### 7.3 随机测试 +- riscv-torture (随机指令生成) + +### 7.4 系统测试 +- CoreMark, Dhrystone (性能基准测试) +- Linux boot (最终目标) + +--- + +## 8. Chisel实现模块划分 + +``` +src/main/scala/ +├── common/ +│ ├── Consts.scala // 常量定义 +│ ├── Parameters.scala // 参数配置 +│ └── Bundles.scala // 数据结构定义 +├── frontend/ +│ ├── ICache.scala +│ ├── ITLB.scala +│ ├── BranchPredictor.scala // Gshare +│ ├── BTB.scala +│ ├── RAS.scala +│ └── Frontend.scala // 前端顶层 +├── decode/ +│ ├── Decoder.scala +│ └── IDStage.scala +├── rename/ +│ ├── RenameTable.scala +│ ├── FreeList.scala +│ ├── ROB.scala +│ └── RenameStage.scala +├── issue/ +│ ├── ReservationStation.scala +│ ├── IssueQueue.scala +│ └── IssueStage.scala +├── execute/ +│ ├── ALU.scala +│ ├── BranchUnit.scala +│ ├── Multiplier.scala +│ ├── Divider.scala +│ ├── FPU.scala +│ └── ExecStage.scala +├── memory/ +│ ├── DCache.scala +│ ├── DTLB.scala +│ ├── MMU.scala +│ ├── LSU.scala // Load/Store Unit +│ ├── LoadQueue.scala +│ ├── StoreQueue.scala +│ └── MemStage.scala +├── writeback/ +│ └── WriteBackStage.scala +├── commit/ +│ └── CommitStage.scala +├── regfile/ +│ └── PhysicalRegFile.scala +├── csr/ +│ ├── CSRFile.scala +│ └── PrivilegeControl.scala +└── Core.scala // 处理器顶层 +``` + +--- + +## 9. 参考资料 + +- **RISC-V Spec**: https://riscv.org/technical/specifications/ +- **Sv39 Paging**: RISC-V Privileged Spec Chapter 4 +- **Gshare Predictor**: "Two-Level Adaptive Training Branch Prediction" (Yeh & Patt, 1991) +- **BOOM Processor**: Berkeley Out-of-Order Machine (参考设计) +- **Rocket Chip**: 顺序发射RISC-V处理器(前端参考) diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..4923c12 --- /dev/null +++ b/build.sbt @@ -0,0 +1,22 @@ +ThisBuild / scalaVersion := "2.13.18" +ThisBuild / version := "0.1.0" +ThisBuild / organization := "edu.riscv" + +val chiselVersion = "7.7.0" + +lazy val root = (project in file(".")) + .settings( + name := "riscv-ooo-core", + libraryDependencies ++= Seq( + "org.chipsalliance" %% "chisel" % chiselVersion, + "org.scalatest" %% "scalatest" % "3.2.19" % "test", + ), + scalacOptions ++= Seq( + "-language:reflectiveCalls", + "-deprecation", + "-feature", + "-Xcheckinit", + "-Ymacro-annotations", + ), + addCompilerPlugin("org.chipsalliance" % "chisel-plugin" % chiselVersion cross CrossVersion.full), + ) diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..e8a1e24 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.9.7 diff --git a/sim/README.md b/sim/README.md new file mode 100644 index 0000000..4489e5e --- /dev/null +++ b/sim/README.md @@ -0,0 +1,98 @@ +# RISC-V Processor Simulation Environment + +## 目录结构 + +``` +sim/ +├── verilator/ # Verilator仿真环境 +│ ├── testbench.cpp # 主测试平台 +│ ├── memory.cpp/h # 内存模型 +│ └── Makefile # 构建脚本 +├── scripts/ # 测试脚本 +│ └── run_tests.sh # 批量测试运行器 +└── waves/ # 波形文件输出目录 +``` + +## 快速开始 + +### 1. 编译测试平台 + +```bash +cd sim/verilator +make compile +``` + +这会: +- 从Chisel生成Verilog代码 +- 使用Verilator编译C++测试平台 + +### 2. 运行单个测试 + +```bash +make run TEST=../../riscv-tests/isa/rv64ui-p-add +``` + +或直接运行: + +```bash +./obj_dir/VCore ../../riscv-tests/isa/rv64ui-p-add +``` + +### 3. 运行所有测试 + +```bash +cd ../scripts +./run_tests.sh +``` + +查看结果: + +```bash +cat test_results.txt +``` + +## 测试结果说明 + +- **PASS**: 测试通过 (tohost写入1) +- **FAIL**: 测试失败 (tohost写入错误码) +- **TIMEOUT**: 测试超时 (超过100,000周期) + +## tohost/fromhost 协议 + +- **tohost地址**: 0x80001000 +- **fromhost地址**: 0x80001040 +- **内存基址**: 0x80000000 +- **内存大小**: 64MB + +测试程序通过向tohost地址写入来报告结果: +- `tohost = 1`: 测试通过 +- `tohost = (error_code << 1) | 1`: 测试失败 + +## 依赖项 + +- Verilator (>= 4.0) +- Scala/SBT (Chisel编译) +- C++14编译器 +- libelf-dev (ELF文件解析) + +## 故障排查 + +### 编译错误 + +如果Verilator编译失败,检查: +1. Verilog是否正确生成在 `generated/` 目录 +2. Core模块的IO接口定义 + +### 测试超时 + +如果测试一直超时: +1. 增加 `MAX_CYCLES` 在 testbench.cpp +2. 检查处理器是否正确执行指令 +3. 验证内存接口握手逻辑 + +### 内存访问错误 + +检查: +1. ELF加载是否成功 +2. 内存地址映射是否正确 +3. 链接脚本中的地址与 `MEM_BASE` 一致 diff --git a/sim/scripts/run_tests.sh b/sim/scripts/run_tests.sh new file mode 100755 index 0000000..2741a8f --- /dev/null +++ b/sim/scripts/run_tests.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +TESTBENCH="../verilator/obj_dir/VCore" +TESTS_DIR="../../riscv-tests/isa" +RESULTS_FILE="test_results.txt" + +if [ ! -x "$TESTBENCH" ]; then + echo "Error: Testbench not found. Run 'make compile' first." + exit 1 +fi + +echo "Running RISC-V tests..." > $RESULTS_FILE +echo "====================" >> $RESULTS_FILE + +PASS=0 +FAIL=0 +TIMEOUT=0 + +for test in $TESTS_DIR/rv64ui-p-* $TESTS_DIR/rv64um-p-* $TESTS_DIR/rv64ua-p-*; do + # Skip dump files + [[ $test == *.dump ]] && continue + [ ! -f "$test" ] && continue + + testname=$(basename $test) + echo -n "Running $testname... " + + # Run with 5 second timeout + output=$(timeout 5s $TESTBENCH $test 2>&1) + exitcode=$? + + if [ $exitcode -eq 0 ]; then + echo "PASS" + echo "$testname: PASS" >> $RESULTS_FILE + ((PASS++)) + elif [ $exitcode -eq 124 ]; then + echo "TIMEOUT" + echo "$testname: TIMEOUT" >> $RESULTS_FILE + ((TIMEOUT++)) + else + echo "FAIL (exit code $exitcode)" + echo "$testname: FAIL" >> $RESULTS_FILE + ((FAIL++)) + fi +done + +echo "" >> $RESULTS_FILE +echo "====================" >> $RESULTS_FILE +echo "Summary:" >> $RESULTS_FILE +echo " PASS: $PASS" >> $RESULTS_FILE +echo " FAIL: $FAIL" >> $RESULTS_FILE +echo " TIMEOUT: $TIMEOUT" >> $RESULTS_FILE +echo " TOTAL: $((PASS + FAIL + TIMEOUT))" >> $RESULTS_FILE + +echo "" +echo "Summary:" +echo " PASS: $PASS" +echo " FAIL: $FAIL" +echo " TIMEOUT: $TIMEOUT" +echo " TOTAL: $((PASS + FAIL + TIMEOUT))" +echo "" +echo "Results saved to $RESULTS_FILE" diff --git a/sim/verilator/Makefile b/sim/verilator/Makefile new file mode 100644 index 0000000..2e638f0 --- /dev/null +++ b/sim/verilator/Makefile @@ -0,0 +1,43 @@ +VERILATOR = verilator +VERILATOR_FLAGS = --cc --exe --build -Wall --trace -Wno-fatal +VERILATOR_FLAGS += -CFLAGS "-std=c++14 -O2" +SBT = env SBT_OPTS="-Dsbt.boot.directory=/tmp/sbt-boot -Dsbt.ivy.home=/tmp/sbt-ivy" COURSIER_CACHE=/tmp/coursier-cache sbt + +CHISEL_DIR = ../.. +GENERATED_DIR = $(CHISEL_DIR)/generated +SRC_FILES = testbench.cpp memory.cpp + +VERILOG_FILES = $(GENERATED_DIR)/Core.sv + +TARGET = obj_dir/VCore + +.PHONY: all verilog compile run clean + +all: compile + +verilog: + @echo "Generating Verilog from Chisel..." + cd $(CHISEL_DIR) && $(SBT) "runMain Core" + +compile: verilog + @echo "Compiling with Verilator..." + $(VERILATOR) $(VERILATOR_FLAGS) \ + -I$(GENERATED_DIR) \ + --top-module Core \ + -o VCore \ + $(VERILOG_FILES) $(SRC_FILES) + +run: compile + @if [ -z "$(TEST)" ]; then \ + echo "Usage: make run TEST="; \ + exit 1; \ + fi + ./$(TARGET) $(TEST) + +test-simple: compile + @echo "Running simple test..." + ./$(TARGET) ../../riscv-tests/isa/rv64ui-p-simple + +clean: + rm -rf obj_dir + rm -rf $(GENERATED_DIR) diff --git a/sim/verilator/memory.cpp b/sim/verilator/memory.cpp new file mode 100644 index 0000000..b62916d --- /dev/null +++ b/sim/verilator/memory.cpp @@ -0,0 +1,111 @@ +#include "memory.h" +#include +#include +#include +#include +#include +#include + +Memory::Memory() : base_addr(MEM_BASE), entry_point(MEM_BASE) { + mem.resize(MEM_SIZE, 0); +} + +uint64_t Memory::to_offset(uint64_t addr) { + if (addr < base_addr || addr >= base_addr + MEM_SIZE) { + fprintf(stderr, "Memory access out of bounds: 0x%lx\n", addr); + return 0; + } + return addr - base_addr; +} + +uint8_t Memory::read8(uint64_t addr) { + return mem[to_offset(addr)]; +} + +uint16_t Memory::read16(uint64_t addr) { + uint64_t off = to_offset(addr); + return mem[off] | (mem[off + 1] << 8); +} + +uint32_t Memory::read32(uint64_t addr) { + uint64_t off = to_offset(addr); + return mem[off] | (mem[off + 1] << 8) | (mem[off + 2] << 16) | (mem[off + 3] << 24); +} + +uint64_t Memory::read64(uint64_t addr) { + uint64_t off = to_offset(addr); + uint64_t lo = read32(addr); + uint64_t hi = read32(addr + 4); + return lo | (hi << 32); +} + +void Memory::write8(uint64_t addr, uint8_t data) { + mem[to_offset(addr)] = data; +} + +void Memory::write16(uint64_t addr, uint16_t data) { + uint64_t off = to_offset(addr); + mem[off] = data & 0xFF; + mem[off + 1] = (data >> 8) & 0xFF; +} + +void Memory::write32(uint64_t addr, uint32_t data) { + uint64_t off = to_offset(addr); + mem[off] = data & 0xFF; + mem[off + 1] = (data >> 8) & 0xFF; + mem[off + 2] = (data >> 16) & 0xFF; + mem[off + 3] = (data >> 24) & 0xFF; +} + +void Memory::write64(uint64_t addr, uint64_t data) { + write32(addr, data & 0xFFFFFFFF); + write32(addr + 4, (data >> 32) & 0xFFFFFFFF); +} + +bool Memory::load_elf(const std::string& filename) { + int fd = open(filename.c_str(), O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Failed to open ELF file: %s\n", filename.c_str()); + return false; + } + + Elf64_Ehdr ehdr; + if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr)) { + fprintf(stderr, "Failed to read ELF header\n"); + close(fd); + return false; + } + + if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0) { + fprintf(stderr, "Not a valid ELF file\n"); + close(fd); + return false; + } + + entry_point = ehdr.e_entry; + + // Load program headers + for (int i = 0; i < ehdr.e_phnum; i++) { + Elf64_Phdr phdr; + lseek(fd, ehdr.e_phoff + i * sizeof(phdr), SEEK_SET); + if (read(fd, &phdr, sizeof(phdr)) != sizeof(phdr)) { + fprintf(stderr, "Failed to read program header %d\n", i); + continue; + } + + if (phdr.p_type == PT_LOAD) { + uint64_t offset = to_offset(phdr.p_paddr); + lseek(fd, phdr.p_offset, SEEK_SET); + if (read(fd, &mem[offset], phdr.p_filesz) != (ssize_t)phdr.p_filesz) { + fprintf(stderr, "Failed to load segment %d\n", i); + close(fd); + return false; + } + printf("Loaded segment: paddr=0x%lx size=%lu\n", phdr.p_paddr, phdr.p_filesz); + } + } + + close(fd); + printf("ELF loaded: entry=0x%lx\n", entry_point); + return true; +} diff --git a/sim/verilator/memory.h b/sim/verilator/memory.h new file mode 100644 index 0000000..0e9b23d --- /dev/null +++ b/sim/verilator/memory.h @@ -0,0 +1,40 @@ +#ifndef MEMORY_H +#define MEMORY_H + +#include +#include +#include + +#define TOHOST_ADDR 0x80001000ULL +#define FROMHOST_ADDR 0x80001040ULL +#define MEM_BASE 0x80000000ULL +#define MEM_SIZE (64 * 1024 * 1024) // 64MB + +class Memory { +private: + std::vector mem; + uint64_t base_addr; + +public: + Memory(); + + bool load_elf(const std::string& filename); + + uint8_t read8(uint64_t addr); + uint16_t read16(uint64_t addr); + uint32_t read32(uint64_t addr); + uint64_t read64(uint64_t addr); + + void write8(uint64_t addr, uint8_t data); + void write16(uint64_t addr, uint16_t data); + void write32(uint64_t addr, uint32_t data); + void write64(uint64_t addr, uint64_t data); + + uint64_t get_entry_point() const { return entry_point; } + +private: + uint64_t entry_point; + uint64_t to_offset(uint64_t addr); +}; + +#endif diff --git a/sim/verilator/testbench.cpp b/sim/verilator/testbench.cpp new file mode 100644 index 0000000..352fcf0 --- /dev/null +++ b/sim/verilator/testbench.cpp @@ -0,0 +1,101 @@ +#include +#include "VCore.h" +#include "memory.h" +#include +#include + +#define MAX_CYCLES 100000 + +int main(int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + Verilated::commandArgs(argc, argv); + + VCore* core = new VCore; + Memory* mem = new Memory(); + + if (!mem->load_elf(argv[1])) { + fprintf(stderr, "Failed to load test binary\n"); + return 1; + } + + // Reset + core->reset = 1; + core->clock = 0; + core->eval(); + core->clock = 1; + core->eval(); + core->clock = 0; + core->reset = 0; + core->eval(); + + uint64_t cycle = 0; + bool test_done = false; + int exit_code = 0; + + while (cycle < MAX_CYCLES && !test_done) { + // Handle instruction memory interface + if (core->io_imem_req_valid) { + uint64_t pc = core->io_imem_req_bits; + core->io_imem_resp_valid = 1; + core->io_imem_resp_bits_0 = mem->read32(pc); + core->io_imem_resp_bits_1 = mem->read32(pc + 4); + } else { + core->io_imem_resp_valid = 0; + } + + // Handle data memory interface + if (core->io_dmem_req_valid) { + uint64_t addr = core->io_dmem_req_bits_addr; + + // Check for tohost write + if (core->io_dmem_req_bits_isStore && addr == TOHOST_ADDR) { + uint64_t tohost = core->io_dmem_req_bits_data; + if (tohost == 1) { + printf("[%lu] TEST PASSED\n", cycle); + test_done = true; + exit_code = 0; + } else if (tohost & 1) { + printf("[%lu] TEST FAILED: error code %lu\n", cycle, tohost >> 1); + test_done = true; + exit_code = 1; + } + } + + if (core->io_dmem_req_bits_isStore) { + switch (core->io_dmem_req_bits_size) { + case 0: mem->write8(addr, core->io_dmem_req_bits_data & 0xff); break; + case 1: mem->write16(addr, core->io_dmem_req_bits_data & 0xffff); break; + case 2: mem->write32(addr, core->io_dmem_req_bits_data & 0xffffffff); break; + default: mem->write64(addr, core->io_dmem_req_bits_data); break; + } + core->io_dmem_resp_valid = 0; + } else { + core->io_dmem_resp_bits = mem->read64(addr); + core->io_dmem_resp_valid = 1; + } + } else { + core->io_dmem_resp_valid = 0; + } + + // Clock cycle + core->clock = 0; + core->eval(); + core->clock = 1; + core->eval(); + + cycle++; + } + + if (!test_done) { + printf("[%lu] TEST TIMEOUT\n", cycle); + exit_code = 2; + } + + delete core; + delete mem; + return exit_code; +} diff --git a/src/main/scala/Core.scala b/src/main/scala/Core.scala new file mode 100644 index 0000000..a4f63b2 --- /dev/null +++ b/src/main/scala/Core.scala @@ -0,0 +1,173 @@ +import chisel3._ +import chisel3.util._ +import _root_.circt.stage.ChiselStage + +class Core(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val imem_req_valid = Output(Bool()) + val imem_req_bits = Output(UInt(p.xlen.W)) + val imem_resp_valid = Input(Bool()) + val imem_resp_bits_0 = Input(UInt(32.W)) + val imem_resp_bits_1 = Input(UInt(32.W)) + + val dmem_req_valid = Output(Bool()) + val dmem_req_bits_addr = Output(UInt(p.xlen.W)) + val dmem_req_bits_data = Output(UInt(p.xlen.W)) + val dmem_req_bits_isStore = Output(Bool()) + val dmem_req_bits_size = Output(UInt(3.W)) + val dmem_resp_valid = Input(Bool()) + val dmem_resp_bits = Input(UInt(p.xlen.W)) + }) + + val sFetch :: sExec :: sLoadWait :: Nil = Enum(3) + val state = RegInit(sFetch) + val pc = RegInit(Consts.ResetVector) + val instReg = RegInit(0.U(32.W)) + val pcReg = RegInit(Consts.ResetVector) + + val regs = RegInit(VecInit(Seq.fill(32)(0.U(p.xlen.W)))) + + val decoder = Module(new Decoder(p)) + decoder.io.pc := pcReg + decoder.io.inst := instReg + val dec = decoder.io.out + + val alu = Module(new ALU(p)) + val branch = Module(new BranchUnit(p)) + val csr = Module(new CSRFile(p)) + + def regRead(addr: UInt): UInt = Mux(addr === 0.U, 0.U, regs(addr)) + def lowLoad(data: UInt, size: UInt, signed: Bool): UInt = { + val b = data(7, 0) + val h = data(15, 0) + val w = data(31, 0) + MuxLookup(size, data)(Seq( + 0.U -> Mux(signed, Consts.signExtend(b, 8), b), + 1.U -> Mux(signed, Consts.signExtend(h, 16), h), + 2.U -> Mux(signed, Consts.signExtend(w, 32), w), + 3.U -> data + )) + } + + val src1 = regRead(dec.rs1) + val src2 = regRead(dec.rs2) + val aluB = Mux(dec.isOpImm || dec.isJalr || dec.isLoad, dec.immI, src2) + + alu.io.fn := dec.aluFn + alu.io.a := src1 + alu.io.b := aluB + alu.io.isWord := dec.isWord + branch.io.funct3 := dec.funct3 + branch.io.a := src1 + branch.io.b := src2 + + csr.io.cmd.valid := state === sExec && dec.isSystem && dec.funct3 =/= 0.U && + !(dec.funct3(1) && dec.rs1 === 0.U) + csr.io.cmd.addr := instReg(31, 20) + csr.io.cmd.cmd := dec.funct3 + csr.io.cmd.rs1 := src1 + csr.io.cmd.zimm := dec.rs1 + val isEcall = instReg === "h00000073".U + val isEbreak = instReg === "h00100073".U + val isMret = instReg === "h30200073".U + val takeTrap = state === sExec && (isEcall || isEbreak) + csr.io.trap := takeTrap + csr.io.trapPc := pcReg + csr.io.trapCause := Mux(isEbreak, 3.U, 11.U) + + val loadAddr = src1 + dec.immI + val storeAddr = src1 + dec.immS + val pendingLoadAddr = Reg(UInt(p.xlen.W)) + val pendingLoadRd = Reg(UInt(5.W)) + val pendingLoadSize = Reg(UInt(3.W)) + val pendingLoadSigned = Reg(Bool()) + + val isAmo = instReg(6, 0) === "b0101111".U + val amoOp = instReg(31, 27) + val amoLoadLike = isAmo && amoOp === "b00010".U + val amoStoreLike = isAmo && amoOp === "b00011".U + + val memReqInExec = state === sExec && (dec.isLoad || dec.isStore) + io.imem_req_valid := state === sFetch + io.imem_req_bits := pc + io.dmem_req_valid := memReqInExec || state === sLoadWait + io.dmem_req_bits_addr := Mux(state === sLoadWait, pendingLoadAddr, Mux(dec.isStore, storeAddr, loadAddr)) + io.dmem_req_bits_data := src2 + io.dmem_req_bits_isStore := state === sExec && Mux(isAmo, !amoLoadLike, dec.isStore) + io.dmem_req_bits_size := Mux(state === sLoadWait, pendingLoadSize, dec.memWidth) + + val branchTarget = pcReg + dec.immB + val jalTarget = pcReg + dec.immJ + val jalrTarget = (src1 + dec.immI) & (~1.U(p.xlen.W)) + val branchTaken = dec.isBranch && branch.io.taken + val nextPc = Mux(dec.isJal, jalTarget, + Mux(dec.isJalr, jalrTarget, + Mux(branchTaken, branchTarget, pcReg + 4.U))) + + val execWriteData = WireDefault(alu.io.out) + when(dec.isLui) { + execWriteData := dec.immU + }.elsewhen(dec.isAuipc) { + execWriteData := pcReg + dec.immU + }.elsewhen(dec.isJal || dec.isJalr) { + execWriteData := pcReg + 4.U + }.elsewhen(dec.isSystem && dec.funct3 =/= 0.U) { + execWriteData := csr.io.rdata + } + + when(state === sFetch) { + when(io.imem_resp_valid) { + instReg := io.imem_resp_bits_0 + pcReg := pc + state := sExec + } + }.elsewhen(state === sExec) { + when(takeTrap) { + pc := csr.io.mtvec + state := sFetch + }.elsewhen(isMret) { + pc := csr.io.mepc + state := sFetch + }.elsewhen(dec.isLoad && !amoStoreLike) { + when(io.dmem_resp_valid) { + when(dec.rd =/= 0.U) { + regs(dec.rd) := lowLoad(io.dmem_resp_bits, dec.memWidth, dec.memSigned) + } + pc := pcReg + 4.U + state := sFetch + }.otherwise { + pendingLoadAddr := loadAddr + pendingLoadRd := dec.rd + pendingLoadSize := dec.memWidth + pendingLoadSigned := dec.memSigned + state := sLoadWait + } + }.elsewhen(dec.isStore || amoStoreLike) { + pc := pcReg + 4.U + state := sFetch + }.otherwise { + when(dec.writesRd && dec.rd =/= 0.U) { + regs(dec.rd) := execWriteData + } + pc := nextPc + state := sFetch + } + }.elsewhen(state === sLoadWait) { + when(io.dmem_resp_valid) { + when(pendingLoadRd =/= 0.U) { + regs(pendingLoadRd) := lowLoad(io.dmem_resp_bits, pendingLoadSize, pendingLoadSigned) + } + pc := pcReg + 4.U + state := sFetch + } + } + +} + +object Core extends App { + ChiselStage.emitSystemVerilogFile( + new Core(), + args = Array("--target-dir", "generated"), + firtoolOpts = Array("-disable-all-randomization", "-strip-debug-info") + ) +} diff --git a/src/main/scala/commit/CommitStage.scala b/src/main/scala/commit/CommitStage.scala new file mode 100644 index 0000000..031ee93 --- /dev/null +++ b/src/main/scala/commit/CommitStage.scala @@ -0,0 +1,18 @@ +import chisel3._ +import chisel3.util._ + +class CommitStage(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val robValid = Input(Bool()) + val robEntry = Input(new RobEntry(p)) + val commitReady = Output(Bool()) + val freeOldPhys = Output(Bool()) + val oldPhys = Output(UInt(log2Ceil(p.physRegs).W)) + val flush = Output(Bool()) + }) + + io.commitReady := io.robValid + io.freeOldPhys := io.robValid && io.robEntry.oldDest =/= io.robEntry.dest + io.oldPhys := io.robEntry.oldDest + io.flush := io.robValid && (io.robEntry.exception || io.robEntry.branchMispredict) +} diff --git a/src/main/scala/common/Bundles.scala b/src/main/scala/common/Bundles.scala new file mode 100644 index 0000000..6a037ac --- /dev/null +++ b/src/main/scala/common/Bundles.scala @@ -0,0 +1,76 @@ +import chisel3._ +import chisel3.util._ + +class FetchPacket(p: CoreParams = CoreParams()) extends Bundle { + val pc = UInt(p.xlen.W) + val inst = Vec(p.fetchWidth, UInt(32.W)) + val predictedTaken = Bool() + val predictedTarget = UInt(p.xlen.W) +} + +class BranchUpdate(p: CoreParams = CoreParams()) extends Bundle { + val valid = Bool() + val pc = UInt(p.xlen.W) + val taken = Bool() + val target = UInt(p.xlen.W) + val isCall = Bool() + val isReturn = Bool() +} + +class DecodedInst(p: CoreParams = CoreParams()) extends Bundle { + val valid = Bool() + val pc = UInt(p.xlen.W) + val inst = UInt(32.W) + val rs1 = UInt(5.W) + val rs2 = UInt(5.W) + val rd = UInt(5.W) + val funct3 = UInt(3.W) + val funct7 = UInt(7.W) + val immI = UInt(p.xlen.W) + val immS = UInt(p.xlen.W) + val immB = UInt(p.xlen.W) + val immU = UInt(p.xlen.W) + val immJ = UInt(p.xlen.W) + val opClass = UInt(Consts.OpClassWidth.W) + val aluFn = UInt(5.W) + val memWidth = UInt(3.W) + val memSigned = Bool() + val isLoad = Bool() + val isStore = Bool() + val isBranch = Bool() + val isJal = Bool() + val isJalr = Bool() + val isLui = Bool() + val isAuipc = Bool() + val isOpImm = Bool() + val isOp = Bool() + val isWord = Bool() + val isSystem = Bool() + val writesRd = Bool() + val illegal = Bool() +} + +class RenamePacket(p: CoreParams = CoreParams()) extends Bundle { + val valid = Bool() + val decoded = new DecodedInst(p) + val prs1 = UInt(log2Ceil(p.physRegs).W) + val prs2 = UInt(log2Ceil(p.physRegs).W) + val prd = UInt(log2Ceil(p.physRegs).W) + val oldPrd = UInt(log2Ceil(p.physRegs).W) + val robIdx = UInt(log2Ceil(p.robEntries).W) +} + +class MemRequest(p: CoreParams = CoreParams()) extends Bundle { + val addr = UInt(p.xlen.W) + val data = UInt(p.xlen.W) + val isStore = Bool() + val size = UInt(3.W) +} + +class CsrCommand(p: CoreParams = CoreParams()) extends Bundle { + val valid = Bool() + val addr = UInt(12.W) + val cmd = UInt(3.W) + val rs1 = UInt(p.xlen.W) + val zimm = UInt(5.W) +} diff --git a/src/main/scala/common/Consts.scala b/src/main/scala/common/Consts.scala new file mode 100644 index 0000000..0f8ca07 --- /dev/null +++ b/src/main/scala/common/Consts.scala @@ -0,0 +1,35 @@ +import chisel3._ +import chisel3.util._ + +object Consts { + val ResetVector = "h80000000".U(64.W) + + val OpClassWidth = 4 + val OP_NONE = 0.U(OpClassWidth.W) + val OP_ALU = 1.U(OpClassWidth.W) + val OP_BRANCH = 2.U(OpClassWidth.W) + val OP_LOAD = 3.U(OpClassWidth.W) + val OP_STORE = 4.U(OpClassWidth.W) + val OP_SYSTEM = 5.U(OpClassWidth.W) + val OP_FP = 6.U(OpClassWidth.W) + + val ALU_ADD = 0.U(5.W) + val ALU_SUB = 1.U(5.W) + val ALU_SLL = 2.U(5.W) + val ALU_SLT = 3.U(5.W) + val ALU_SLTU = 4.U(5.W) + val ALU_XOR = 5.U(5.W) + val ALU_SRL = 6.U(5.W) + val ALU_SRA = 7.U(5.W) + val ALU_OR = 8.U(5.W) + val ALU_AND = 9.U(5.W) + val ALU_MUL = 10.U(5.W) + val ALU_DIV = 11.U(5.W) + val ALU_DIVU = 12.U(5.W) + val ALU_REM = 13.U(5.W) + val ALU_REMU = 14.U(5.W) + val ALU_COPY_B = 15.U(5.W) + + def signExtend(value: UInt, from: Int, to: Int = 64): UInt = + Cat(Fill(to - from, value(from - 1)), value(from - 1, 0)) +} diff --git a/src/main/scala/common/Parameters.scala b/src/main/scala/common/Parameters.scala new file mode 100644 index 0000000..6ec256f --- /dev/null +++ b/src/main/scala/common/Parameters.scala @@ -0,0 +1,28 @@ +case class CoreParams( + xlen: Int = 64, + fetchWidth: Int = 2, + issueWidth: Int = 2, + archRegs: Int = 32, + physRegs: Int = 64, + robEntries: Int = 64, + intRsEntries: Int = 16, + lsuRsEntries: Int = 12, + fpRsEntries: Int = 16, + loadQueueEntries: Int = 16, + storeQueueEntries: Int = 16, + btbEntries: Int = 512, + rasEntries: Int = 16, + ghrBits: Int = 12, + iCacheBytes: Int = 32 * 1024, + iCacheWays: Int = 4, + dCacheBytes: Int = 32 * 1024, + dCacheWays: Int = 8, + cacheLineBytes: Int = 64, + itlbEntries: Int = 32, + dtlbEntries: Int = 32 +) { + require(xlen == 64, "this implementation targets RV64") + require(fetchWidth == 2, "frontend is parameterized around dual fetch") + require(issueWidth == 2, "backend structures are parameterized around dual issue") +} + diff --git a/src/main/scala/csr/CSRFile.scala b/src/main/scala/csr/CSRFile.scala new file mode 100644 index 0000000..09ed7c3 --- /dev/null +++ b/src/main/scala/csr/CSRFile.scala @@ -0,0 +1,99 @@ +import chisel3._ +import chisel3.util._ + +class CSRFile(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val cmd = Input(new CsrCommand(p)) + val rdata = Output(UInt(p.xlen.W)) + val trap = Input(Bool()) + val trapPc = Input(UInt(p.xlen.W)) + val trapCause = Input(UInt(p.xlen.W)) + val satp = Output(UInt(p.xlen.W)) + val mtvec = Output(UInt(p.xlen.W)) + val mepc = Output(UInt(p.xlen.W)) + }) + + val cycle = RegInit(0.U(p.xlen.W)) + val instret = RegInit(0.U(p.xlen.W)) + val mstatus = RegInit(0.U(p.xlen.W)) + val misa = RegInit("h800000000014112d".U(p.xlen.W)) + val mtvecReg = RegInit(0.U(p.xlen.W)) + val mepcReg = RegInit(0.U(p.xlen.W)) + val mcause = RegInit(0.U(p.xlen.W)) + val mtval = RegInit(0.U(p.xlen.W)) + val medeleg = RegInit(0.U(p.xlen.W)) + val mideleg = RegInit(0.U(p.xlen.W)) + val mie = RegInit(0.U(p.xlen.W)) + val mip = RegInit(0.U(p.xlen.W)) + val sstatus = RegInit(0.U(p.xlen.W)) + val stvec = RegInit(0.U(p.xlen.W)) + val sepc = RegInit(0.U(p.xlen.W)) + val scause = RegInit(0.U(p.xlen.W)) + val stval = RegInit(0.U(p.xlen.W)) + val sscratch = RegInit(0.U(p.xlen.W)) + val satpReg = RegInit(0.U(p.xlen.W)) + + cycle := cycle + 1.U + io.satp := satpReg + io.mtvec := mtvecReg + io.mepc := mepcReg + + val r = WireDefault(0.U(p.xlen.W)) + switch(io.cmd.addr) { + is("h300".U) { r := mstatus } + is("h301".U) { r := misa } + is("h302".U) { r := medeleg } + is("h303".U) { r := mideleg } + is("h304".U) { r := mie } + is("h305".U) { r := mtvecReg } + is("h341".U) { r := mepcReg } + is("h342".U) { r := mcause } + is("h343".U) { r := mtval } + is("h344".U) { r := mip } + is("h100".U) { r := sstatus } + is("h105".U) { r := stvec } + is("h140".U) { r := sscratch } + is("h141".U) { r := sepc } + is("h142".U) { r := scause } + is("h143".U) { r := stval } + is("h180".U) { r := satpReg } + is("hf14".U) { r := 0.U } + is("hc00".U) { r := cycle } + is("hc01".U) { r := 0.U } + is("hc02".U) { r := instret } + } + io.rdata := r + + val operand = Mux(io.cmd.cmd(2), io.cmd.zimm, io.cmd.rs1) + val next = MuxLookup(io.cmd.cmd(1, 0), r)(Seq( + 1.U -> operand, + 2.U -> (r | operand), + 3.U -> (r & ~operand) + )) + + when(io.cmd.valid && io.cmd.cmd =/= 0.U) { + switch(io.cmd.addr) { + is("h300".U) { mstatus := next } + is("h302".U) { medeleg := next } + is("h303".U) { mideleg := next } + is("h304".U) { mie := next } + is("h305".U) { mtvecReg := next } + is("h341".U) { mepcReg := next } + is("h342".U) { mcause := next } + is("h343".U) { mtval := next } + is("h344".U) { mip := next } + is("h100".U) { sstatus := next } + is("h105".U) { stvec := next } + is("h140".U) { sscratch := next } + is("h141".U) { sepc := next } + is("h142".U) { scause := next } + is("h143".U) { stval := next } + is("h180".U) { satpReg := next } + } + } + + when(io.trap) { + mepcReg := io.trapPc + mcause := io.trapCause + } +} diff --git a/src/main/scala/csr/PrivilegeControl.scala b/src/main/scala/csr/PrivilegeControl.scala new file mode 100644 index 0000000..64a5f4d --- /dev/null +++ b/src/main/scala/csr/PrivilegeControl.scala @@ -0,0 +1,21 @@ +import chisel3._ + +class PrivilegeControl(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val trap = Input(Bool()) + val mret = Input(Bool()) + val sret = Input(Bool()) + val privilege = Output(UInt(2.W)) + }) + + val mode = RegInit(3.U(2.W)) + when(io.trap) { + mode := 3.U + }.elsewhen(io.mret) { + mode := 0.U + }.elsewhen(io.sret) { + mode := 0.U + } + io.privilege := mode +} + diff --git a/src/main/scala/decode/Decoder.scala b/src/main/scala/decode/Decoder.scala new file mode 100644 index 0000000..8dfa67f --- /dev/null +++ b/src/main/scala/decode/Decoder.scala @@ -0,0 +1,150 @@ +import chisel3._ +import chisel3.util._ + +class Decoder(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val pc = Input(UInt(p.xlen.W)) + val inst = Input(UInt(32.W)) + val out = Output(new DecodedInst(p)) + }) + + val opcode = io.inst(6, 0) + val rd = io.inst(11, 7) + val funct3 = io.inst(14, 12) + val rs1 = io.inst(19, 15) + val rs2 = io.inst(24, 20) + val funct7 = io.inst(31, 25) + + val immI = Consts.signExtend(io.inst(31, 20), 12) + val immS = Consts.signExtend(Cat(io.inst(31, 25), io.inst(11, 7)), 12) + val immB = Consts.signExtend(Cat(io.inst(31), io.inst(7), io.inst(30, 25), io.inst(11, 8), 0.U(1.W)), 13) + val immU = Consts.signExtend(Cat(io.inst(31, 12), 0.U(12.W)), 32) + val immJ = Consts.signExtend(Cat(io.inst(31), io.inst(19, 12), io.inst(20), io.inst(30, 21), 0.U(1.W)), 21) + + val d = WireDefault(0.U.asTypeOf(new DecodedInst(p))) + d.valid := true.B + d.pc := io.pc + d.inst := io.inst + d.rs1 := rs1 + d.rs2 := rs2 + d.rd := rd + d.funct3 := funct3 + d.funct7 := funct7 + d.immI := immI + d.immS := immS + d.immB := immB + d.immU := immU + d.immJ := immJ + d.memWidth := MuxLookup(funct3, 3.U)(Seq( + "b000".U -> 0.U, + "b001".U -> 1.U, + "b010".U -> 2.U, + "b011".U -> 3.U, + "b100".U -> 0.U, + "b101".U -> 1.U, + "b110".U -> 2.U + )) + d.memSigned := !funct3(2) + d.illegal := false.B + + switch(opcode) { + is("b0110111".U) { + d.isLui := true.B + d.writesRd := rd =/= 0.U + d.opClass := Consts.OP_ALU + d.aluFn := Consts.ALU_COPY_B + } + is("b0010111".U) { + d.isAuipc := true.B + d.writesRd := rd =/= 0.U + d.opClass := Consts.OP_ALU + d.aluFn := Consts.ALU_ADD + } + is("b1101111".U) { + d.isJal := true.B + d.writesRd := rd =/= 0.U + d.opClass := Consts.OP_BRANCH + } + is("b1100111".U) { + d.isJalr := true.B + d.writesRd := rd =/= 0.U + d.opClass := Consts.OP_BRANCH + } + is("b1100011".U) { + d.isBranch := true.B + d.opClass := Consts.OP_BRANCH + } + is("b0000011".U) { + d.isLoad := true.B + d.writesRd := rd =/= 0.U + d.opClass := Consts.OP_LOAD + } + is("b0100011".U) { + d.isStore := true.B + d.opClass := Consts.OP_STORE + } + is("b0010011".U, "b0011011".U) { + d.isOpImm := true.B + d.isWord := opcode === "b0011011".U + d.writesRd := rd =/= 0.U + d.opClass := Consts.OP_ALU + d.aluFn := MuxLookup(funct3, Consts.ALU_ADD)(Seq( + "b000".U -> Consts.ALU_ADD, + "b001".U -> Consts.ALU_SLL, + "b010".U -> Consts.ALU_SLT, + "b011".U -> Consts.ALU_SLTU, + "b100".U -> Consts.ALU_XOR, + "b101".U -> Mux(funct7(5), Consts.ALU_SRA, Consts.ALU_SRL), + "b110".U -> Consts.ALU_OR, + "b111".U -> Consts.ALU_AND + )) + } + is("b0110011".U, "b0111011".U) { + d.isOp := true.B + d.isWord := opcode === "b0111011".U + d.writesRd := rd =/= 0.U + d.opClass := Consts.OP_ALU + d.aluFn := Mux(funct7 === "b0000001".U, MuxLookup(funct3, Consts.ALU_MUL)(Seq( + "b000".U -> Consts.ALU_MUL, + "b100".U -> Consts.ALU_DIV, + "b101".U -> Consts.ALU_DIVU, + "b110".U -> Consts.ALU_REM, + "b111".U -> Consts.ALU_REMU + )), MuxLookup(funct3, Consts.ALU_ADD)(Seq( + "b000".U -> Mux(funct7(5), Consts.ALU_SUB, Consts.ALU_ADD), + "b001".U -> Consts.ALU_SLL, + "b010".U -> Consts.ALU_SLT, + "b011".U -> Consts.ALU_SLTU, + "b100".U -> Consts.ALU_XOR, + "b101".U -> Mux(funct7(5), Consts.ALU_SRA, Consts.ALU_SRL), + "b110".U -> Consts.ALU_OR, + "b111".U -> Consts.ALU_AND + ))) + } + is("b0001111".U) { + d.opClass := Consts.OP_SYSTEM + } + is("b1110011".U) { + d.isSystem := true.B + d.writesRd := rd =/= 0.U && funct3 =/= 0.U + d.opClass := Consts.OP_SYSTEM + } + is("b0101111".U) { + d.isLoad := true.B + d.isStore := true.B + d.writesRd := rd =/= 0.U + d.memWidth := Mux(funct3 === "b010".U, 2.U, 3.U) + d.opClass := Consts.OP_LOAD + } + } + + when(opcode =/= "b0110111".U && opcode =/= "b0010111".U && opcode =/= "b1101111".U && + opcode =/= "b1100111".U && opcode =/= "b1100011".U && opcode =/= "b0000011".U && + opcode =/= "b0100011".U && opcode =/= "b0010011".U && opcode =/= "b0011011".U && + opcode =/= "b0110011".U && opcode =/= "b0111011".U && opcode =/= "b0001111".U && + opcode =/= "b1110011".U && opcode =/= "b0101111".U) { + d.illegal := true.B + } + + io.out := d +} diff --git a/src/main/scala/decode/IDStage.scala b/src/main/scala/decode/IDStage.scala new file mode 100644 index 0000000..8b4b8ce --- /dev/null +++ b/src/main/scala/decode/IDStage.scala @@ -0,0 +1,20 @@ +import chisel3._ + +class IDStage(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val inValid = Input(Bool()) + val in = Input(new FetchPacket(p)) + val outValid = Output(Vec(p.fetchWidth, Bool())) + val out = Output(Vec(p.fetchWidth, new DecodedInst(p))) + }) + + val decoders = Seq.fill(p.fetchWidth)(Module(new Decoder(p))) + for (i <- 0 until p.fetchWidth) { + decoders(i).io.pc := io.in.pc + (4 * i).U + decoders(i).io.inst := io.in.inst(i) + io.out(i) := decoders(i).io.out + io.out(i).valid := io.inValid + io.outValid(i) := io.inValid + } +} + diff --git a/src/main/scala/execute/ALU.scala b/src/main/scala/execute/ALU.scala new file mode 100644 index 0000000..3f8e753 --- /dev/null +++ b/src/main/scala/execute/ALU.scala @@ -0,0 +1,36 @@ +import chisel3._ +import chisel3.util._ + +class ALU(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val fn = Input(UInt(5.W)) + val a = Input(UInt(p.xlen.W)) + val b = Input(UInt(p.xlen.W)) + val isWord = Input(Bool()) + val out = Output(UInt(p.xlen.W)) + }) + + val shamt = Mux(io.isWord, io.b(4, 0), io.b(5, 0)) + val raw = WireDefault(0.U(p.xlen.W)) + switch(io.fn) { + is(Consts.ALU_ADD) { raw := io.a + io.b } + is(Consts.ALU_SUB) { raw := io.a - io.b } + is(Consts.ALU_SLL) { raw := io.a << shamt } + is(Consts.ALU_SLT) { raw := (io.a.asSInt < io.b.asSInt).asUInt } + is(Consts.ALU_SLTU) { raw := io.a < io.b } + is(Consts.ALU_XOR) { raw := io.a ^ io.b } + is(Consts.ALU_SRL) { raw := io.a >> shamt } + is(Consts.ALU_SRA) { raw := (io.a.asSInt >> shamt).asUInt } + is(Consts.ALU_OR) { raw := io.a | io.b } + is(Consts.ALU_AND) { raw := io.a & io.b } + is(Consts.ALU_MUL) { raw := (io.a * io.b)(p.xlen - 1, 0) } + is(Consts.ALU_DIV) { raw := Mux(io.b === 0.U, Fill(p.xlen, 1.U), (io.a.asSInt / io.b.asSInt).asUInt) } + is(Consts.ALU_DIVU) { raw := Mux(io.b === 0.U, Fill(p.xlen, 1.U), io.a / io.b) } + is(Consts.ALU_REM) { raw := Mux(io.b === 0.U, io.a, (io.a.asSInt % io.b.asSInt).asUInt) } + is(Consts.ALU_REMU) { raw := Mux(io.b === 0.U, io.a, io.a % io.b) } + is(Consts.ALU_COPY_B) { raw := io.b } + } + + io.out := Mux(io.isWord, Consts.signExtend(raw(31, 0), 32), raw) +} + diff --git a/src/main/scala/execute/BranchUnit.scala b/src/main/scala/execute/BranchUnit.scala new file mode 100644 index 0000000..97c97d0 --- /dev/null +++ b/src/main/scala/execute/BranchUnit.scala @@ -0,0 +1,21 @@ +import chisel3._ +import chisel3.util._ + +class BranchUnit(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val funct3 = Input(UInt(3.W)) + val a = Input(UInt(p.xlen.W)) + val b = Input(UInt(p.xlen.W)) + val taken = Output(Bool()) + }) + + io.taken := MuxLookup(io.funct3, false.B)(Seq( + "b000".U -> (io.a === io.b), + "b001".U -> (io.a =/= io.b), + "b100".U -> (io.a.asSInt < io.b.asSInt), + "b101".U -> (io.a.asSInt >= io.b.asSInt), + "b110".U -> (io.a < io.b), + "b111".U -> (io.a >= io.b) + )) +} + diff --git a/src/main/scala/execute/Divider.scala b/src/main/scala/execute/Divider.scala new file mode 100644 index 0000000..0f111f7 --- /dev/null +++ b/src/main/scala/execute/Divider.scala @@ -0,0 +1,14 @@ +import chisel3._ +import chisel3.util._ + +class Divider(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val signed = Input(Bool()) + val a = Input(UInt(p.xlen.W)) + val b = Input(UInt(p.xlen.W)) + val quotient = Output(UInt(p.xlen.W)) + val remainder = Output(UInt(p.xlen.W)) + }) + io.quotient := Mux(io.b === 0.U, Fill(p.xlen, 1.U), Mux(io.signed, (io.a.asSInt / io.b.asSInt).asUInt, io.a / io.b)) + io.remainder := Mux(io.b === 0.U, io.a, Mux(io.signed, (io.a.asSInt % io.b.asSInt).asUInt, io.a % io.b)) +} diff --git a/src/main/scala/execute/ExecStage.scala b/src/main/scala/execute/ExecStage.scala new file mode 100644 index 0000000..f8bd5e2 --- /dev/null +++ b/src/main/scala/execute/ExecStage.scala @@ -0,0 +1,29 @@ +import chisel3._ + +class ExecStage(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val inValid = Input(Bool()) + val in = Input(new DecodedInst(p)) + val src1 = Input(UInt(p.xlen.W)) + val src2 = Input(UInt(p.xlen.W)) + val outValid = Output(Bool()) + val result = Output(UInt(p.xlen.W)) + val branchTaken = Output(Bool()) + }) + + val alu = Module(new ALU(p)) + val branch = Module(new BranchUnit(p)) + + alu.io.fn := io.in.aluFn + alu.io.a := io.src1 + alu.io.b := io.src2 + alu.io.isWord := io.in.isWord + branch.io.funct3 := io.in.funct3 + branch.io.a := io.src1 + branch.io.b := io.src2 + + io.outValid := io.inValid + io.result := alu.io.out + io.branchTaken := branch.io.taken +} + diff --git a/src/main/scala/execute/FPU.scala b/src/main/scala/execute/FPU.scala new file mode 100644 index 0000000..46509c7 --- /dev/null +++ b/src/main/scala/execute/FPU.scala @@ -0,0 +1,16 @@ +import chisel3._ + +class FPU(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val inValid = Input(Bool()) + val op = Input(UInt(4.W)) + val a = Input(UInt(p.xlen.W)) + val b = Input(UInt(p.xlen.W)) + val outValid = Output(Bool()) + val out = Output(UInt(p.xlen.W)) + }) + + io.outValid := io.inValid + io.out := 0.U +} + diff --git a/src/main/scala/execute/Multiplier.scala b/src/main/scala/execute/Multiplier.scala new file mode 100644 index 0000000..e00a586 --- /dev/null +++ b/src/main/scala/execute/Multiplier.scala @@ -0,0 +1,11 @@ +import chisel3._ + +class Multiplier(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val a = Input(UInt(p.xlen.W)) + val b = Input(UInt(p.xlen.W)) + val out = Output(UInt(p.xlen.W)) + }) + io.out := (io.a * io.b)(p.xlen - 1, 0) +} + diff --git a/src/main/scala/frontend/BTB.scala b/src/main/scala/frontend/BTB.scala new file mode 100644 index 0000000..58a3280 --- /dev/null +++ b/src/main/scala/frontend/BTB.scala @@ -0,0 +1,32 @@ +import chisel3._ +import chisel3.util._ + +class BTB(p: CoreParams = CoreParams()) extends Module { + private val idxBits = log2Ceil(p.btbEntries) + private val tagBits = 20 + + val io = IO(new Bundle { + val pc = Input(UInt(p.xlen.W)) + val hit = Output(Bool()) + val target = Output(UInt(p.xlen.W)) + val update = Input(new BranchUpdate(p)) + }) + + val valid = RegInit(VecInit(Seq.fill(p.btbEntries)(false.B))) + val tags = Reg(Vec(p.btbEntries, UInt(tagBits.W))) + val targets = Reg(Vec(p.btbEntries, UInt(p.xlen.W))) + + val idx = io.pc(idxBits + 1, 2) + val tag = io.pc(idxBits + tagBits + 1, idxBits + 2) + + io.hit := valid(idx) && tags(idx) === tag + io.target := targets(idx) + + when(io.update.valid && io.update.taken) { + val uidx = io.update.pc(idxBits + 1, 2) + valid(uidx) := true.B + tags(uidx) := io.update.pc(idxBits + tagBits + 1, idxBits + 2) + targets(uidx) := io.update.target + } +} + diff --git a/src/main/scala/frontend/BranchPredictor.scala b/src/main/scala/frontend/BranchPredictor.scala new file mode 100644 index 0000000..9a30e19 --- /dev/null +++ b/src/main/scala/frontend/BranchPredictor.scala @@ -0,0 +1,38 @@ +import chisel3._ +import chisel3.util._ + +class BranchPredictor(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val pc = Input(UInt(p.xlen.W)) + val taken = Output(Bool()) + val target = Output(UInt(p.xlen.W)) + val update = Input(new BranchUpdate(p)) + }) + + val ghr = RegInit(0.U(p.ghrBits.W)) + val pht = RegInit(VecInit(Seq.fill(1 << p.ghrBits)(1.U(2.W)))) + val btb = Module(new BTB(p)) + val ras = Module(new RAS(p)) + + val idx = io.pc(p.ghrBits + 1, 2) ^ ghr + btb.io.pc := io.pc + btb.io.update := io.update + + ras.io.push := io.update.valid && io.update.isCall + ras.io.pop := io.update.valid && io.update.isReturn + ras.io.pushAddr := io.update.pc + 4.U + + io.taken := pht(idx)(1) && btb.io.hit + io.target := Mux(io.update.isReturn && !ras.io.empty, ras.io.top, btb.io.target) + + when(io.update.valid) { + val uidx = io.update.pc(p.ghrBits + 1, 2) ^ ghr + when(io.update.taken && pht(uidx) =/= 3.U) { + pht(uidx) := pht(uidx) + 1.U + }.elsewhen(!io.update.taken && pht(uidx) =/= 0.U) { + pht(uidx) := pht(uidx) - 1.U + } + ghr := Cat(ghr(p.ghrBits - 2, 0), io.update.taken) + } +} + diff --git a/src/main/scala/frontend/Frontend.scala b/src/main/scala/frontend/Frontend.scala new file mode 100644 index 0000000..fce25e2 --- /dev/null +++ b/src/main/scala/frontend/Frontend.scala @@ -0,0 +1,43 @@ +import chisel3._ + +class Frontend(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val redirectValid = Input(Bool()) + val redirectPc = Input(UInt(p.xlen.W)) + val imemReqValid = Output(Bool()) + val imemReqAddr = Output(UInt(p.xlen.W)) + val imemRespValid = Input(Bool()) + val imemRespBits = Input(Vec(p.fetchWidth, UInt(32.W))) + val outValid = Output(Bool()) + val out = Output(new FetchPacket(p)) + val branchUpdate = Input(new BranchUpdate(p)) + }) + + val pc = RegInit(Consts.ResetVector) + val predictor = Module(new BranchPredictor(p)) + val itlb = Module(new ITLB(p)) + val icache = Module(new ICache(p)) + + predictor.io.pc := pc + predictor.io.update := io.branchUpdate + itlb.io.vaddr := pc + + icache.io.reqValid := true.B + icache.io.reqAddr := itlb.io.paddr + icache.io.memRespValid := io.imemRespValid + icache.io.memRespBits := io.imemRespBits + + io.imemReqValid := icache.io.memReqValid + io.imemReqAddr := icache.io.memReqAddr + io.outValid := icache.io.respValid + io.out := icache.io.resp + io.out.predictedTaken := predictor.io.taken + io.out.predictedTarget := predictor.io.target + + when(io.redirectValid) { + pc := io.redirectPc + }.elsewhen(icache.io.respValid) { + pc := Mux(predictor.io.taken, predictor.io.target, pc + (4 * p.fetchWidth).U) + } +} + diff --git a/src/main/scala/frontend/ICache.scala b/src/main/scala/frontend/ICache.scala new file mode 100644 index 0000000..09c5873 --- /dev/null +++ b/src/main/scala/frontend/ICache.scala @@ -0,0 +1,23 @@ +import chisel3._ + +class ICache(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val reqValid = Input(Bool()) + val reqAddr = Input(UInt(p.xlen.W)) + val memReqValid = Output(Bool()) + val memReqAddr = Output(UInt(p.xlen.W)) + val memRespValid = Input(Bool()) + val memRespBits = Input(Vec(p.fetchWidth, UInt(32.W))) + val respValid = Output(Bool()) + val resp = Output(new FetchPacket(p)) + }) + + io.memReqValid := io.reqValid + io.memReqAddr := io.reqAddr + io.respValid := io.memRespValid + io.resp.pc := io.reqAddr + io.resp.inst := io.memRespBits + io.resp.predictedTaken := false.B + io.resp.predictedTarget := io.reqAddr + (4 * p.fetchWidth).U +} + diff --git a/src/main/scala/frontend/ITLB.scala b/src/main/scala/frontend/ITLB.scala new file mode 100644 index 0000000..274dcd0 --- /dev/null +++ b/src/main/scala/frontend/ITLB.scala @@ -0,0 +1,16 @@ +import chisel3._ +import chisel3.util._ + +class ITLB(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val vaddr = Input(UInt(p.xlen.W)) + val paddr = Output(UInt(p.xlen.W)) + val hit = Output(Bool()) + val miss = Output(Bool()) + }) + + io.paddr := io.vaddr + io.hit := true.B + io.miss := false.B +} + diff --git a/src/main/scala/frontend/RAS.scala b/src/main/scala/frontend/RAS.scala new file mode 100644 index 0000000..ad56b62 --- /dev/null +++ b/src/main/scala/frontend/RAS.scala @@ -0,0 +1,31 @@ +import chisel3._ +import chisel3.util._ + +class RAS(p: CoreParams = CoreParams()) extends Module { + private val ptrBits = log2Ceil(p.rasEntries) + + val io = IO(new Bundle { + val push = Input(Bool()) + val pop = Input(Bool()) + val pushAddr = Input(UInt(p.xlen.W)) + val top = Output(UInt(p.xlen.W)) + val empty = Output(Bool()) + }) + + val stack = Reg(Vec(p.rasEntries, UInt(p.xlen.W))) + val sp = RegInit(0.U(ptrBits.W)) + val count = RegInit(0.U(log2Ceil(p.rasEntries + 1).W)) + + io.empty := count === 0.U + io.top := stack(Mux(sp === 0.U, (p.rasEntries - 1).U, sp - 1.U)) + + when(io.push) { + stack(sp) := io.pushAddr + sp := Mux(sp === (p.rasEntries - 1).U, 0.U, sp + 1.U) + when(count =/= p.rasEntries.U) { count := count + 1.U } + }.elsewhen(io.pop && count =/= 0.U) { + sp := Mux(sp === 0.U, (p.rasEntries - 1).U, sp - 1.U) + count := count - 1.U + } +} + diff --git a/src/main/scala/issue/IssueQueue.scala b/src/main/scala/issue/IssueQueue.scala new file mode 100644 index 0000000..3074e44 --- /dev/null +++ b/src/main/scala/issue/IssueQueue.scala @@ -0,0 +1,24 @@ +import chisel3._ + +class IssueQueue(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val enqValid = Input(Bool()) + val enq = Input(new RenamePacket(p)) + val enqReady = Output(Bool()) + val issueValid = Output(Bool()) + val issue = Output(new RenamePacket(p)) + val issueReady = Input(Bool()) + val flush = Input(Bool()) + }) + + val intRs = Module(new ReservationStation(p, p.intRsEntries)) + intRs.io.enqValid := io.enqValid + intRs.io.enq := io.enq + intRs.io.issueReady := io.issueReady + intRs.io.flush := io.flush + + io.enqReady := intRs.io.enqReady + io.issueValid := intRs.io.issueValid + io.issue := intRs.io.issue +} + diff --git a/src/main/scala/issue/IssueStage.scala b/src/main/scala/issue/IssueStage.scala new file mode 100644 index 0000000..4f060d2 --- /dev/null +++ b/src/main/scala/issue/IssueStage.scala @@ -0,0 +1,24 @@ +import chisel3._ + +class IssueStage(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val inValid = Input(Bool()) + val in = Input(new RenamePacket(p)) + val inReady = Output(Bool()) + val outValid = Output(Bool()) + val out = Output(new RenamePacket(p)) + val outReady = Input(Bool()) + val flush = Input(Bool()) + }) + + val queue = Module(new IssueQueue(p)) + queue.io.enqValid := io.inValid + queue.io.enq := io.in + queue.io.issueReady := io.outReady + queue.io.flush := io.flush + + io.inReady := queue.io.enqReady + io.outValid := queue.io.issueValid + io.out := queue.io.issue +} + diff --git a/src/main/scala/issue/ReservationStation.scala b/src/main/scala/issue/ReservationStation.scala new file mode 100644 index 0000000..eec9457 --- /dev/null +++ b/src/main/scala/issue/ReservationStation.scala @@ -0,0 +1,24 @@ +import chisel3._ +import chisel3.util._ + +class ReservationStation(p: CoreParams = CoreParams(), entries: Int = 16) extends Module { + val io = IO(new Bundle { + val enqValid = Input(Bool()) + val enq = Input(new RenamePacket(p)) + val enqReady = Output(Bool()) + val issueValid = Output(Bool()) + val issue = Output(new RenamePacket(p)) + val issueReady = Input(Bool()) + val flush = Input(Bool()) + }) + + val q = Module(new Queue(new RenamePacket(p), entries, flow = false, pipe = true)) + q.io.enq.valid := io.enqValid + q.io.enq.bits := io.enq + q.io.deq.ready := io.issueReady || io.flush + + io.enqReady := q.io.enq.ready + io.issueValid := q.io.deq.valid && !io.flush + io.issue := q.io.deq.bits +} + diff --git a/src/main/scala/memory/DCache.scala b/src/main/scala/memory/DCache.scala new file mode 100644 index 0000000..82b89c7 --- /dev/null +++ b/src/main/scala/memory/DCache.scala @@ -0,0 +1,20 @@ +import chisel3._ + +class DCache(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val reqValid = Input(Bool()) + val req = Input(new MemRequest(p)) + val memReqValid = Output(Bool()) + val memReq = Output(new MemRequest(p)) + val memRespValid = Input(Bool()) + val memRespData = Input(UInt(p.xlen.W)) + val respValid = Output(Bool()) + val respData = Output(UInt(p.xlen.W)) + }) + + io.memReqValid := io.reqValid + io.memReq := io.req + io.respValid := io.memRespValid + io.respData := io.memRespData +} + diff --git a/src/main/scala/memory/DTLB.scala b/src/main/scala/memory/DTLB.scala new file mode 100644 index 0000000..2a8838a --- /dev/null +++ b/src/main/scala/memory/DTLB.scala @@ -0,0 +1,15 @@ +import chisel3._ + +class DTLB(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val vaddr = Input(UInt(p.xlen.W)) + val paddr = Output(UInt(p.xlen.W)) + val hit = Output(Bool()) + val miss = Output(Bool()) + }) + + io.paddr := io.vaddr + io.hit := true.B + io.miss := false.B +} + diff --git a/src/main/scala/memory/LSU.scala b/src/main/scala/memory/LSU.scala new file mode 100644 index 0000000..0004eb7 --- /dev/null +++ b/src/main/scala/memory/LSU.scala @@ -0,0 +1,30 @@ +import chisel3._ + +class LSU(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val reqValid = Input(Bool()) + val req = Input(new MemRequest(p)) + val dmemReqValid = Output(Bool()) + val dmemReq = Output(new MemRequest(p)) + val dmemRespValid = Input(Bool()) + val dmemRespData = Input(UInt(p.xlen.W)) + val respValid = Output(Bool()) + val respData = Output(UInt(p.xlen.W)) + }) + + val dtlb = Module(new DTLB(p)) + val dcache = Module(new DCache(p)) + + dtlb.io.vaddr := io.req.addr + dcache.io.reqValid := io.reqValid + dcache.io.req := io.req + dcache.io.req.addr := dtlb.io.paddr + dcache.io.memRespValid := io.dmemRespValid + dcache.io.memRespData := io.dmemRespData + + io.dmemReqValid := dcache.io.memReqValid + io.dmemReq := dcache.io.memReq + io.respValid := dcache.io.respValid + io.respData := dcache.io.respData +} + diff --git a/src/main/scala/memory/LoadQueue.scala b/src/main/scala/memory/LoadQueue.scala new file mode 100644 index 0000000..fabbcf7 --- /dev/null +++ b/src/main/scala/memory/LoadQueue.scala @@ -0,0 +1,23 @@ +import chisel3._ +import chisel3.util._ + +class LoadQueue(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val enqValid = Input(Bool()) + val enqAddr = Input(UInt(p.xlen.W)) + val enqReady = Output(Bool()) + val complete = Input(Bool()) + val flush = Input(Bool()) + }) + + val count = RegInit(0.U(log2Ceil(p.loadQueueEntries + 1).W)) + io.enqReady := count =/= p.loadQueueEntries.U + + when(io.flush) { + count := 0.U + }.otherwise { + when(io.enqValid && io.enqReady) { count := count + 1.U } + when(io.complete && count =/= 0.U) { count := count - 1.U } + } +} + diff --git a/src/main/scala/memory/MMU.scala b/src/main/scala/memory/MMU.scala new file mode 100644 index 0000000..2152f27 --- /dev/null +++ b/src/main/scala/memory/MMU.scala @@ -0,0 +1,15 @@ +import chisel3._ + +class MMU(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val satp = Input(UInt(p.xlen.W)) + val vaddr = Input(UInt(p.xlen.W)) + val isStore = Input(Bool()) + val paddr = Output(UInt(p.xlen.W)) + val pageFault = Output(Bool()) + }) + + io.paddr := io.vaddr + io.pageFault := false.B +} + diff --git a/src/main/scala/memory/MemStage.scala b/src/main/scala/memory/MemStage.scala new file mode 100644 index 0000000..a7a798d --- /dev/null +++ b/src/main/scala/memory/MemStage.scala @@ -0,0 +1,26 @@ +import chisel3._ + +class MemStage(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val reqValid = Input(Bool()) + val req = Input(new MemRequest(p)) + val dmemReqValid = Output(Bool()) + val dmemReq = Output(new MemRequest(p)) + val dmemRespValid = Input(Bool()) + val dmemRespData = Input(UInt(p.xlen.W)) + val respValid = Output(Bool()) + val respData = Output(UInt(p.xlen.W)) + }) + + val lsu = Module(new LSU(p)) + lsu.io.reqValid := io.reqValid + lsu.io.req := io.req + lsu.io.dmemRespValid := io.dmemRespValid + lsu.io.dmemRespData := io.dmemRespData + + io.dmemReqValid := lsu.io.dmemReqValid + io.dmemReq := lsu.io.dmemReq + io.respValid := lsu.io.respValid + io.respData := lsu.io.respData +} + diff --git a/src/main/scala/memory/StoreQueue.scala b/src/main/scala/memory/StoreQueue.scala new file mode 100644 index 0000000..ceefdf1 --- /dev/null +++ b/src/main/scala/memory/StoreQueue.scala @@ -0,0 +1,24 @@ +import chisel3._ +import chisel3.util._ + +class StoreQueue(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val enqValid = Input(Bool()) + val enq = Input(new MemRequest(p)) + val enqReady = Output(Bool()) + val drainValid = Output(Bool()) + val drain = Output(new MemRequest(p)) + val drainReady = Input(Bool()) + val flush = Input(Bool()) + }) + + val q = Module(new Queue(new MemRequest(p), p.storeQueueEntries)) + q.io.enq.valid := io.enqValid && !io.flush + q.io.enq.bits := io.enq + q.io.deq.ready := io.drainReady || io.flush + + io.enqReady := q.io.enq.ready + io.drainValid := q.io.deq.valid && !io.flush + io.drain := q.io.deq.bits +} + diff --git a/src/main/scala/regfile/PhysicalRegFile.scala b/src/main/scala/regfile/PhysicalRegFile.scala new file mode 100644 index 0000000..e363f1b --- /dev/null +++ b/src/main/scala/regfile/PhysicalRegFile.scala @@ -0,0 +1,23 @@ +import chisel3._ +import chisel3.util._ + +class PhysicalRegFile(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val raddr = Input(Vec(4, UInt(log2Ceil(p.physRegs).W))) + val rdata = Output(Vec(4, UInt(p.xlen.W))) + val wen = Input(Vec(2, Bool())) + val waddr = Input(Vec(2, UInt(log2Ceil(p.physRegs).W))) + val wdata = Input(Vec(2, UInt(p.xlen.W))) + }) + + val regs = RegInit(VecInit(Seq.fill(p.physRegs)(0.U(p.xlen.W)))) + for (i <- 0 until 4) { + io.rdata(i) := Mux(io.raddr(i) === 0.U, 0.U, regs(io.raddr(i))) + } + for (i <- 0 until 2) { + when(io.wen(i) && io.waddr(i) =/= 0.U) { + regs(io.waddr(i)) := io.wdata(i) + } + } +} + diff --git a/src/main/scala/rename/FreeList.scala b/src/main/scala/rename/FreeList.scala new file mode 100644 index 0000000..5ea0400 --- /dev/null +++ b/src/main/scala/rename/FreeList.scala @@ -0,0 +1,26 @@ +import chisel3._ +import chisel3.util._ + +class FreeList(p: CoreParams = CoreParams()) extends Module { + private val physBits = log2Ceil(p.physRegs) + val io = IO(new Bundle { + val alloc = Input(Bool()) + val allocPhys = Output(UInt(physBits.W)) + val canAlloc = Output(Bool()) + val free = Input(Bool()) + val freePhys = Input(UInt(physBits.W)) + }) + + val freeBits = RegInit(VecInit((0 until p.physRegs).map(i => (i >= p.archRegs).B))) + val chosen = PriorityEncoder(freeBits) + io.canAlloc := freeBits.asUInt.orR + io.allocPhys := chosen + + when(io.alloc && io.canAlloc) { + freeBits(chosen) := false.B + } + when(io.free && io.freePhys >= p.archRegs.U) { + freeBits(io.freePhys) := true.B + } +} + diff --git a/src/main/scala/rename/ROB.scala b/src/main/scala/rename/ROB.scala new file mode 100644 index 0000000..cc0d6db --- /dev/null +++ b/src/main/scala/rename/ROB.scala @@ -0,0 +1,71 @@ +import chisel3._ +import chisel3.util._ + +class RobEntry(p: CoreParams = CoreParams()) extends Bundle { + val valid = Bool() + val pc = UInt(p.xlen.W) + val opClass = UInt(Consts.OpClassWidth.W) + val dest = UInt(log2Ceil(p.physRegs).W) + val oldDest = UInt(log2Ceil(p.physRegs).W) + val completed = Bool() + val exception = Bool() + val branchMispredict = Bool() +} + +class ROB(p: CoreParams = CoreParams()) extends Module { + private val idxBits = log2Ceil(p.robEntries) + val io = IO(new Bundle { + val allocate = Input(Bool()) + val allocatePc = Input(UInt(p.xlen.W)) + val allocateClass = Input(UInt(Consts.OpClassWidth.W)) + val allocateDest = Input(UInt(log2Ceil(p.physRegs).W)) + val allocateOldDest = Input(UInt(log2Ceil(p.physRegs).W)) + val allocateIdx = Output(UInt(idxBits.W)) + val canAllocate = Output(Bool()) + val complete = Input(Bool()) + val completeIdx = Input(UInt(idxBits.W)) + val commitValid = Output(Bool()) + val commit = Output(new RobEntry(p)) + val commitReady = Input(Bool()) + val flush = Input(Bool()) + }) + + val entries = RegInit(VecInit(Seq.fill(p.robEntries)(0.U.asTypeOf(new RobEntry(p))))) + val head = RegInit(0.U(idxBits.W)) + val tail = RegInit(0.U(idxBits.W)) + val count = RegInit(0.U(log2Ceil(p.robEntries + 1).W)) + + io.canAllocate := count =/= p.robEntries.U + io.allocateIdx := tail + io.commit := entries(head) + io.commitValid := count =/= 0.U && entries(head).valid && entries(head).completed + + when(io.flush) { + entries.foreach(_.valid := false.B) + head := 0.U + tail := 0.U + count := 0.U + }.otherwise { + when(io.allocate && io.canAllocate) { + entries(tail).valid := true.B + entries(tail).pc := io.allocatePc + entries(tail).opClass := io.allocateClass + entries(tail).dest := io.allocateDest + entries(tail).oldDest := io.allocateOldDest + entries(tail).completed := false.B + entries(tail).exception := false.B + entries(tail).branchMispredict := false.B + tail := tail + 1.U + count := count + 1.U + } + when(io.complete) { + entries(io.completeIdx).completed := true.B + } + when(io.commitValid && io.commitReady) { + entries(head).valid := false.B + head := head + 1.U + count := count - 1.U + } + } +} + diff --git a/src/main/scala/rename/RenameStage.scala b/src/main/scala/rename/RenameStage.scala new file mode 100644 index 0000000..69544f5 --- /dev/null +++ b/src/main/scala/rename/RenameStage.scala @@ -0,0 +1,49 @@ +import chisel3._ +import chisel3.util._ + +class RenameStage(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val inValid = Input(Bool()) + val in = Input(new DecodedInst(p)) + val outValid = Output(Bool()) + val out = Output(new RenamePacket(p)) + val commitFree = Input(Bool()) + val commitOldPhys = Input(UInt(log2Ceil(p.physRegs).W)) + val flush = Input(Bool()) + }) + + val table = Module(new RenameTable(p)) + val freeList = Module(new FreeList(p)) + val rob = Module(new ROB(p)) + + table.io.rs1 := io.in.rs1 + table.io.rs2 := io.in.rs2 + table.io.rd := io.in.rd + table.io.newPhys := freeList.io.allocPhys + table.io.wen := io.inValid && io.in.writesRd && freeList.io.canAlloc && rob.io.canAllocate + table.io.recover := io.flush + + freeList.io.alloc := table.io.wen + freeList.io.free := io.commitFree + freeList.io.freePhys := io.commitOldPhys + + rob.io.allocate := io.inValid && rob.io.canAllocate && (!io.in.writesRd || freeList.io.canAlloc) + rob.io.allocatePc := io.in.pc + rob.io.allocateClass := io.in.opClass + rob.io.allocateDest := Mux(io.in.writesRd, freeList.io.allocPhys, table.io.oldPrd) + rob.io.allocateOldDest := table.io.oldPrd + rob.io.complete := false.B + rob.io.completeIdx := 0.U + rob.io.commitReady := false.B + rob.io.flush := io.flush + + io.outValid := rob.io.allocate + io.out.valid := io.outValid + io.out.decoded := io.in + io.out.prs1 := table.io.prs1 + io.out.prs2 := table.io.prs2 + io.out.prd := Mux(io.in.writesRd, freeList.io.allocPhys, table.io.oldPrd) + io.out.oldPrd := table.io.oldPrd + io.out.robIdx := rob.io.allocateIdx +} + diff --git a/src/main/scala/rename/RenameTable.scala b/src/main/scala/rename/RenameTable.scala new file mode 100644 index 0000000..92007b3 --- /dev/null +++ b/src/main/scala/rename/RenameTable.scala @@ -0,0 +1,32 @@ +import chisel3._ +import chisel3.util._ + +class RenameTable(p: CoreParams = CoreParams()) extends Module { + private val physBits = log2Ceil(p.physRegs) + val io = IO(new Bundle { + val rs1 = Input(UInt(5.W)) + val rs2 = Input(UInt(5.W)) + val rd = Input(UInt(5.W)) + val newPhys = Input(UInt(physBits.W)) + val wen = Input(Bool()) + val prs1 = Output(UInt(physBits.W)) + val prs2 = Output(UInt(physBits.W)) + val oldPrd = Output(UInt(physBits.W)) + val recover = Input(Bool()) + }) + + val init = VecInit((0 until p.archRegs).map(_.U(physBits.W))) + val speculative = RegInit(init) + val committed = RegInit(init) + + io.prs1 := speculative(io.rs1) + io.prs2 := speculative(io.rs2) + io.oldPrd := speculative(io.rd) + + when(io.recover) { + speculative := committed + }.elsewhen(io.wen && io.rd =/= 0.U) { + speculative(io.rd) := io.newPhys + } +} + diff --git a/src/main/scala/writeback/WriteBackStage.scala b/src/main/scala/writeback/WriteBackStage.scala new file mode 100644 index 0000000..79ca0b9 --- /dev/null +++ b/src/main/scala/writeback/WriteBackStage.scala @@ -0,0 +1,18 @@ +import chisel3._ +import chisel3.util._ + +class WriteBackStage(p: CoreParams = CoreParams()) extends Module { + val io = IO(new Bundle { + val valid = Input(Bool()) + val physDest = Input(UInt(log2Ceil(p.physRegs).W)) + val data = Input(UInt(p.xlen.W)) + val wen = Output(Bool()) + val waddr = Output(UInt(log2Ceil(p.physRegs).W)) + val wdata = Output(UInt(p.xlen.W)) + }) + + io.wen := io.valid + io.waddr := io.physDest + io.wdata := io.data +} +