initial commit

This commit is contained in:
2026-04-12 22:20:18 +08:00
commit 190c2edbb2
155 changed files with 36314 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
# LaCC 接口
LaCC(Loongarch32R Custom Coprocessor Interface) 接口是 Open-LA500 用于扩展自定义指令的接口。
# 指令格式
![](./picture/lacc_inst.png)
| 域 | 描述 |
| ------ | --------------------------------------------------- |
| opcode | 固定为1100,用于确定当前指令 |
| command| 用于编码多个自定义指令将发送至lacc接口 |
| imm | 额外的立即数 |
| rj | 第一个寄存器的编码 |
| rk | 第二个寄存器的编码 |
| rd | 目的寄存器编码当目的寄存器不为0时会写回寄存器堆中 |
# 接口定义
> 方向为协处理器视角
| 通道 | 方向 | 宽度 | 信号名 | 描述 |
| -------- | ------ | ------------- | --------------- | ------------------------------------------------------------ |
| 全局 | input | 1 | lacc_flush | 当处理器触发异常或分支预测失败时将该信号置位 |
| 请求 | input | 1 | lacc_req_valid | 处理器核发送请求 |
| 请求 | input | LACC_OP_WIDTH | lacc_req_command| 指令中的command域 |
| 请求 | input | 7 | lacc_req_imm | 指令中的imm域 |
| 请求 | input | 32 | lacc_req_rj | 第一个寄存器的值 |
| 请求 | input | 32 | lacc_req_rk | 第二个寄存器的值 |
| 回复 | output | 1 | lacc_rsp_valid | 指令完成信号,处理器将继续执行 |
| 回复 | output | 32 | lacc_rsp_rdat | 写回寄存器的数据 |
| 访存请求 | output | 1 | lacc_data_valid | 向dcache发送的访存请求信号 |
| 访存请求 | input | 1 | lacc_data_ready | dcache当前是否可以接受请求 |
| 访存请求 | output | 32 | lacc_data_addr | 访存地址 |
| 访存请求 | output | 1 | lacc_data_read | 是否为读请求 |
| 访存请求 | output | 32 | lacc_data_wdata | 写入数据 |
| 访存请求 | output | 2 | lacc_data_size | 访存数据大小 <br>2'b00: byte<br>2'b01: half<br>2'b10: word |
| 访存回复 | input | 1 | lacc_drsp_valid | dcache发送的回复信号<br>写请求将会在第二周期接收到该回复<br>读请求将会在dcache成功后接收 |
| 访存回复 | input | 32 | lacc_drsp_data | 访存得到的数据 |
# 运行流程
1. 在解码阶段解析 lacc 指令,并将 op 和 imm 发送给执行阶段
2. 在执行阶段`lacc_req_valid`为1lacc接口将接受 lacc 指令并暂停,直到`lacc_rsp_valid`为高才会将指令发送给下一级
3. 如果需要访存,可以将 `lacc_data_valid` 置高并设置访存地址及大小等信息。当`lacc_data_valid``lacc_data_ready`同时为高则当前请求成功发送至dcache。
4. 当指令执行完成之后将`lacc_rsp_valid`置高,指令将从 exe 级继续执行
lacc读请求时序,读取地址为0x1C0FFF38读取数据为0x00000000
![](./picture/lacc_read.png)
lacc写请求时序写入地址为0x1C0FFEE8,写入数据为0x70A3A52B
![](./picture/lacc_write.png)
# 自定义指令
添加自定义指令只需两步:
1. 修改`mycpu.h`中的`LACC_OP_SIZE`为自定义指令数量(若`LACC_OP_SIZE`为1需要修改`LACC_OP_WIDTH`为1并且取消`HAS_LACC`的注释
2.`lacc_core.v`中删除示例`lacc_demo`,写入自定指令代码
# demo
demo实现了从两个地址载入向量点乘之后再存入缓存中的功能代码位于`lacc_demo.v`中。
## 指令
| 指令 | op | rj | rk | 描述 |
| -------- | ---- | ----- | ----- | ----------------------------------------------- |
| op_cfg | 1 | size | waddr | 设置写回地址及向量长度 |
| op_lmadd | 0 | addr1 | addr2 | 设置需要计算的两个内存地址对位相加再存入waddr |
## 状态机
| 状态 | 说明 | 转换条件 | 下一状态 |
| --------- | --------------- | ----------------------------------------- | --------- |
| IDLE | | lacc_req_valid & op_lmadd | REQ_ADDR1 |
| REQ_ADDR1 | 访问addr1的数据 | data_hsk(访问addr1数据) | REQ_ADDR2 |
| REQ_ADDR2 | 访问addr2的数据 | data_hsk(访问addr2数据) | FINAL |
| FINAL | 计算结果并写回 | data_hsk & req_size_nz(写入数据且size!=0) | REQ_ADDR1 |
| FINAL | | data_hsk & ~req_size_nz | IDLE |
data_hsk即`lacc_data_valid & lacc_data_ready`,表明当前访存请求发送成功。
**数据控制**
使用buffer_valid信号表示第一个地址的数据是否被接收。wdata_valid表示写回数据准备完成
`buffer_valid & lacc_drsp_valid`为高时说明第二个地址的数据已经返回,在该周期计算`buffer_data+lacc_drsp_rdata`并写入wdata
# 修改编译器
我们可以使用".word xxxxxxx"的格式在汇编中添加自定义指令。但是这种方式阅读不够友好,并且不利于操作数读写,例如
```c
asm volatile (
"move $r5, %[addr]\n\t"
"move $r6, %[para]\n\t"
".word 0xc00018a0\n\t"
::[addr]"r"(addr),[para]"r"(para)
:"$r5", "$r6"
);
```
**修改编译器**
我们可以修改binutils使得编译器可以识别自定义指令。
- 下载loongarch toolchain: https://gitee.com/loongson-edu/la32r-toolchains/tree/master
- 进入src/la32r_binutils/opcodes,打开loongarch-opc.c,在`loongarch_fix_opcodes`结构体中添加
```c
{0xc0000000, 0xf0000000, "lacc", "u22:6,r0:5,r5:5,r10:5,u15:7", 0, 0, 0, 0}
```
- 根据toolchina的README编译并将bin目录添加到path中
自定义指令的格式为:
```
lacc command, rd, rj, rk, imm
```
上例可以修改为:
```c
asm volatile (
"lacc 0x0, $r0, %[addr], %[para], 0x0\n\t"
::[addr]"r"(addr), [para]"r"(para)
);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 73 KiB

View File

@@ -0,0 +1,94 @@
# 分支预测
分支预测即根据分支历史提前预测指令跳转方向及跳转目标。分支预测器种类繁多例如BTBBranch Target Buffer、BHTBranch History Table、RASReturn Address Stack但适用于五级流水的其实并不多。如设计概述中所介绍的五级流水中pfs级发出取指请求fs级取回指令ds级开始译码并明确跳转指令的所有信息。也就表示分支预测只有一拍的空间可以利用且无预译码可依据的信息只有pc跳转方向及跳转目标都只能靠猜。适用的实际上仅有BTB。不过对于较为特殊的jirl指令即寄存器跳转若直接采用BTB的机制会使得命中率极低。因此在BTB的基础上进行改造由CAM表记录jirl指令的pc并由RAS机制预测其跳转目标。
----
接下来介绍分支预测器的具体实现,分为两点:分支预测器的内部实现;分支预测器同流水线的交互。
## 分支预测器设计
分支预测器的逻辑可以分为两个部分由PC进行预测跳转目标及跳转方向的预测部分根据指令译码结果与分支预测器预测结果的比较对分支预测器历史信息进行修正的修正部分。
### 预测部分
首先来看模块的接口。
- fetch_pc取指pc
- fetch_en取指使能
- ret_en预测结果使能
- ret_pc预测跳转目标
- taken预测跳转方向
- ret_index指示当前预测结果的对应项用于修正
btb表有32项包含四个内容
- btb_pc30位用于和取指pc匹配
- btb_target30位存放跳转目标
- btb_counter2位由最高位指示跳转方向
- btb_valid1位当前项是否有效
对于jirl指令的预测逻辑由两部分组成16项的CAM表以及8项的RAS。
CAM表包含两个内容
- ras_pc30位用于和取指pc匹配
- ras_valid1位当前项是否有效
RAS简单来说就是一个栈。存储30位的跳转目标由ras_ptr指示栈顶。
明确表项内容后来看预测逻辑。fetch_en以及fetch_pc进入分支预测器时首先缓存一拍。单看分支预测器缓存插在什么位置其实都可以只要有一拍的延迟即可但考虑到赋值nextpc即fetch_pc的路径非常长因此将缓存插在靠前的位置。缓存后的信号为fetch_en_r及fetch_pc_r将其与两个CAM的PC部分btb_pc和ras_pc进行匹配。该项有效且pc匹配则表示命中。命中项记录在btb_match_rd和ras_match_rd中。
两个表中有一项命中则置起ret_en。若命中在btb CAM表中ret_pc取对应项的btb_target即可若命中在ras CAM表中则取ras栈顶的跳转目标。理论上btb_match_rd和ras_match_rd为one-hot且只会命中在一个表中因此不必使用优先级逻辑。ret_index同理而taken在btb_match时需要看对应项btb_counter最高位而ras_match时直接置为1。预测阶段不会对分支预测器的历史信息进行任何修正。
### 修正部分
在ds译码级汇总分支预测信息以及译码得到的正确分支跳转信息。
预测信息同上:
- ds_btb_target
- ds_btb_index
- ds_btb_taken
- ds_btb_en
正确的分支跳转信息如下:
- br_to_btb表示当前指令是否为分支预测器所覆盖的跳转指令当前结构的分支预测器覆盖所有跳转指令。
- br_taken跳转方向
- br_target跳转目标
译码级根据以上信息,稍加分析并传递至分支预测器,传递信息包括:
- operate_en分支预测器修正操作使能。注意该信号需要在ds_ready_go、es_allowin和ds_valid为1时置起且无例外。只有ds当拍允许流动至下一级时才能发出修正操作。对于ds_ready_go因为流水线中指令寄存器的依赖有可能导致ds阻塞阻塞时指令所得到的寄存器值是错误的即译码得到的分支跳转信息也是错误的。而es_allowin的介入使其操作使能仅维持一拍避免重复操作。
- operate_pc修正操作针对的分支指令的pc
- operate_index修正操作针对的CAM表项
- pop_ras为jirl指令时置起
- push_ras为bl指令时置起
- add_entry当前指令为分支指令但预测器未进行预测时置起。这里与上了一个额外条件br_taken因为当pc在分支预测器中未命中时顺序指令也算是预测为untaken。br_taken不为1时并不需要在预测器中建项
- delete_entry当前指令不为分支指令但预测器进行预测时置起。这种情况出现的概率极低只有在自修改代码中可以不用考虑
- pre_error当前指令为分支指令且预测器进行预测但跳转目标不一致时置起
- pre_right当前指令为分支指令且预测器进行预测且跳转目标一致时置起
- target_error当前指令为分支指令且预测器进行预测且跳转目标一致但跳转目标不一致时置起
- right_orien正确的跳转方向
- right_target正确的跳转目标
分支预测器会根据以上信息进行修正首先由pop_ras即是否为jirl指令区分当前操作的是BTB部分还是RAS部分针对BTB部分操作类型包括
- 建项add_entry置起时。使能指定项并填入operate_pc和right_target以及初始化btb_counter为2'b10。对于填入项即index的选择若BTB CAM表中还留有未使能项则选择其中一项。若所有项都有效则选择btb_counter为2'b00的一项即大概率不跳转的预测项因为未在btb中命中的pc即预测为不跳转两者效果是相同的只不过该pc若下一次为跳转则会重新建项相当于将其btb_counter由2'b00直接转为2'b10很有可能导致下一次的预测出错。但是这种情况出现的概率肯定比替换掉其他btb_counter的值导致下一次预测错的概率低。若所有项都有效且btb_counter都不为2'b00则随机选择一项。
- 修正跳转目标target_error置起时。重新填入right_target并初始化btb_counter为2'b10。
- 修正跳转方向pre_error或者pre_right置起时。根据right_orien调整btb_counter若正确的分支方向为taken则累加若为untaken则递减。
针对RAS部分操作类型包括
- 建项add_entry置起时。使能指定项填入operate_pc即可。相当于预测pc的分支类型。index的选择同BTB部分。
- 进栈当push_ras置起且当前RAS非满时存入当前pc的下一条指令并将指针上移。即当前为bl函数跳转指令其返回地址为bl的下一条指令。RAS指针指向的是空项。
- 出栈当pop_ras置起且当前RAS非空时指针直接下移一项即可。
## 分支预测器与流水线交互逻辑
首先简单介绍取指逻辑该部分内容会在后续逐步展开。来看几个关键信号inst_valid指示当前取指pc是否有效该信号直接送往icache等待接收。inst_valid主要看fs_allowin信号即pfs指令允许向后流动时其取指pc即为有效能够避免相同pc取指请求的重复发出。nextpc即为取指pc当前仅关注其最为常规的pc来源seq_pc顺序取指pc。当inst_valid为1时并不代表取指请求即刻便能发出还需要等待icache空闲由inst_addr_ok表示取指请求的其他去向tlb、btb不需要等待。当inst_valid && inst_addr_ok为1取指请求发出pfs_ready_go置起pfs向fs流动。
fetch_en和fetch_pc发送至分支预测器后下一拍得到预测结果btb_en/btb_ret_pc/btb_taken。当btb_en && btb_taken为1时fetch_btb_target置起分支跳转将nextpc纠正至btb_ret_pc。这段逻辑中有一点需要注意分支预测器返回的结果仅维持一拍但pfs并不一定能够向后流动因此需要缓存分支预测器的返回结果避免信息丢失。分支预测器的返回结果缓存在btb_lock_buffer中由btb_lock_en指示缓存是否有效。缓存条件即为分支返回结果且pfs无法向后流动btb_en && !pfs_ready_go缓存释放条件为下一次取指重新发起。而nextpc的维护以及fs向ds传递分支预测结果时都将采用btb_ret_pc_t、btb_ret_pc_t、btb_ret_pc_t、btb_ret_pc_t信号由分支预测器返回信息和缓存的信息相或得到两者同一时刻仅有一个有效。
译码级根据译码和分支预测的结果能够知道下一条指令取指的方向是否正确。取指级有两个方面的工作需要完成修正分支预测的历史信息若预测错误则需纠正取指方向。此处主要讲述第二方面的内容。若译码级检测到分支预测错误则会置起btb_pre_error_flush信号由该信号修正取指方向该工作可继续细分为两个内容
- 取消掉下一条进入译码级的指令,及错误预测导致错误取指的指令
两种情况需要考虑:
- 当该分支指令向后流动的那一拍btb_pre_error_flush && es_allowin为1时若下一拍下一条指令紧接着进入译码级则可直接取消。不过可以用更粗犷的方法即下一拍的ds_valid直接为0无需识别是否有指令进入。
- 同样的条件若下一拍无指令进入即当拍fs_to_ds_valid为0则置起branch_slot_cancel触发器。该触发器为1时会等待下一条指令进入当检测fs_to_ds_valid为1时将其取消并恢复branch_slot_cancel至0。
- 纠正nextpc。当译码级发现分支预测错误会将正确的跳转目标btb_pre_error_flush_target和使能信号btb_pre_error_flush通过br_bus传递至取指阶段修正nextpc。
需要考虑两个问题:
- 当nextpc修正信息向前传递时pfs并不一定处于能够取指的状态例如inst_addr_ok不为1。此时若译码级不阻塞且pfs不将修正信息存入buffer会导致信息丢失。
- 译码级会记录分支预测错误并取消掉错误预测的分支指令的下一条指令。若pfs在取完预测错误的分支指令后始终处于阻塞状态之后直接被修正至正确的跳转方向则会导致正确的指令被取消掉。
针对上述两个问题可在pfs中构建一个状态机解决。br_target_inst_req_state表示状态。当发现预测错误后根据流水线状态进入不同的状态。
- !fs_valid && !inst_addr_ok预测错误分支指令取出后未进行任何取指因为fs级为空inst_addr_ok从未置起。且当前也不处于可取指的状态。进入br_target_inst_req_wait_slot状态。
- !inst_addr_ok && fs_valid已有一条错误取指的指令在fs中但pfs并不处于能够取指的状态。进入br_target_inst_req_wait_br_target状态。
- inst_addr_ok && !fs_valid预测错误分支指令取出后未进行任何取指当前处于可取指的状态但当前的指令会在译码级取消。进入br_target_inst_req_wait_br_target状态。
br_target_inst_req_wait_slot状态会待取指请求发出后切换至br_target_inst_req_wait_br_target状态该状态便会将nextpc修正至存储btb_pre_error_flush_target的pc bufferbr_target_inst_req_buffer。当取指请求再次发出后状态切换至空状态br_target_inst_req_empty。
构建状态机后还需要注意一个问题。也许pfs处于即刻能够发出取指请求的状态。可粗略的判断fs级是否有指令便改变next_pc同时置起inst_valid即使当前无法取指(inst_addr_ok为0)也没有关系。

View File

@@ -0,0 +1,17 @@
# 前言
OpenLA500齐物是一款实现了`龙芯架构32位精简版指令集LoongArch32 Reduced`的处理器核。OpenLA500的架构为经典的单发射静态五级流水配备两路组相连的指令/数据Cache32项全相连的tlb实现虚实地址映射以及BTB及RAS实现分支预测。基本按照国科大体系结构实验课程逐步设计得到。FPGA运行频率为50M。
OpenLA500经过全面验证已处于稳定状态。并于2023年龙芯全流程平台项目中在SMIC 180um工艺下流片。
以下将从8个方面介绍OpenLA500的设计细节。
- [设计概述](./设计概述.md):基于顶层框图,介绍各级流水的划分和各级流水之间的交互,以及流水线与其他模块的交互。
- [分支预测](./分支预测.md)BTB及RAS两个分支预测器的具体设计以及在流水线中的工作原理。
- [流水线的发射部分]():流水线发射阶段(译码级)操作数准备、前递网络、阻塞控制的设计。
- [流水线的执行部分]()alu、mul、div运算部件在流水线中的实现。
- [MMU]()虚实地址映射逻辑tlb维护。
- [例外实现]():流水线中例外的判断及处理。
- [访存子系统]():指令/数据Cache及AXI总线接口的实现。
- [调试部分]()UART在线调试系统与OpenLA500的交互逻辑。

View File

@@ -0,0 +1,63 @@
# 设计概述
![系统框图](./picture/框图.svg)
系统框图展示了OpenLA500处理器核的内部结构。指令的执行始终按照取指、译码、执行、访存、写回的顺序进行。各级流水会存储指令执行的中间状态在完成当前阶段的操作后将结果存入下一级流水进行下一个阶段的操作。
## 执行流程
处理器核内维护`nextpc`信号,表示取指地址。待取指开始时,`fetch_pc`会发往`icache``tlb``btb`进行不同的工作,以及存入触发器。在下一拍即`fs_stage`取指级,`btb`完成分支预测,将结果直接送往`nextpc`,更改其取指方向。以及由`fetch_pc`以及`tlb`虚实地址翻译的结果,在`icache`指令缓存中得到`inst`指令码,并存入触发器。下一级`ds_stage`译码级在拿到指令码后,便交给`decoder`进行译码。译码后可以得到具体的操作码`op`,以及寄存器号,并可借此在`regfile`寄存器堆或者`csr`控制寄存器中索引到寄存器值`rf_data``es_stage`执行级在得到操作码及操作数后,便可送至`alu`运算部件进行简单运算,以及送往`div`除法部件、`mul`乘法部件进行多拍较复杂的运算。此外,由`alu`可以得到访存地址,并发至`tlb``dcache`数据缓存完成访存请求的处理,基本同`icache`。在`mem_stage`访存级,可得到访存、乘除法的运算结果,并由`op`选择最终的运算结果`final_res`,并存入触发器。`wb_stage`写回级便会将最终运算结果写回到`regfile``csr`。对于`i/dcache`,若取指/访存请求缺失,便会发送请求至`axi_bridge`并通过标准的`AXI3`总线协议与外界通信。
---
流水级的各阶段需要与其他不同的模块进行交互,确认其任务是否完成。以下针对流水级及各模块进行介绍:
## 取指阶段
### `pfs`
该级流水的主要功能是维护`nextpc`,也就是流水线取指的虚地址。其实从更为严谨的角度来看`pfs`并不应该单独称为一级流水,因为`nextpc``wire`类型,或者说`pfs`在正常的执行流程中不会缓存信号,当拍便可获得取指方向。实际上`pfs`为各级能够影响取指地址的流水级的衍生。`nextpc`的维护逻辑可以说是五级流水中最为复杂的一部分,因为`icache`并不是时刻处于就绪的状态,由`inst_addr_ok`信号指示其是否就绪。因此,对于某些特殊情况下出现的转顺即逝的信息,比如流水线刷新等,需要构建状态机缓存信息。待取指方向确认,且`icache`就绪,`pfs`会置起`fetch_en`,紧接着`btb``icache``tlb`便会接收`pfs`发出的`fetch_pc`,开始相应的操作。
### `btb`
分支预测器,根据`PC`直接预测其跳转地址。32项`btb`组织为`CAM`表的形式,由`PC`查询。此外,还包括一个`ras`组织为一个8项的栈存储函数调用过程中的返回地址。`btb`分支预测的结果会在下一拍返回,若预测正确则可消除下一拍产生于`fs`级流水的空泡,预测错误不会造成任何性能损失。
### `icache`
指令缓存,两路组相连的结构。`fetch_pc`进入`icache`后,首先根据由`fetch_pc`拆解而来的`index`部分,索引出两路的`cache`行。其次再根据`tag`部分与`tlb`翻译得到的物理页号是否相等判断是否有命中项。若命中,下一拍返回`inst_rdata`,若未命中,则进入`icache`处理`miss`请求的状态机,`inst_addr_ok`拉低。等待`miss`请求处理完毕后再接收新的取指请求。替换策略为随机替换。
### `tlb`
组织为32项`CAM`表的形式,指令和数据共用,需要两个查询端口。通过`fetch_pc`的虚页号查询是否存在命中项,命中则返回物理页号。若未命中,则会标记该指令存在`tlb`缺失例外,并进入例外处理程序。待对应`tlb`项在页表中找到并由软件填入`tlb`后,重新执行该指令。
### `fs`
`fs`取值级便是真正意义上的第一级流水。`fs`级接收`icache`返回的指令码`inst_rdata`,由`inst_data_ok`指示指令码是否就绪。若`icache miss``fs`级便会阻塞。额外需要注意一点,`tlb`的读取有一拍的延迟因为32项的CAM表会有较长的逻辑。因此`tlb`的读取结果是在`fs`级得到,也就表示`tlb`相关的例外判断是在`fs`级完成,而取指请求是在`pfs`级发出。所以`fs`级需要具备中止上一拍`pfs`级发往`icache`的取指请求的能力,体现在`tlb_excp_cancel_req`信号中,`dcache`同理。
## 译码阶段
### `ds`
译码级,其功能便是将指令码解析为对应的操作码,并根据寄存器号,从`regfile`中取出寄存器值。不过寄存器的最新值可能还未被写入到`regfile`中,因为后续流水线中的指令正在执行,结果未获得或者未写回,而当前指令可能依赖于这些指令的结果。因此译码级与后续流水线构建了前递路径(`es_to_ds_forward_bus/ms_to_ds_forward_bus`),用于前递未写回的结果,或者通知`ds`级结果未获取,需要阻塞。此外,经过译码,已知晓该指令是否为跳转指令,以及是否跳转。也就可以识别分支预测器的预测结果是否正确,`ds`级便可通过`br_bus`对取指方向进行修正,以及更新分支预测器的历史信息。`btb`的预测包括两个方面,该`PC`是否为跳转指令;识别为跳转指令时是否跳转。`ds`级需要鉴别并对分支预测器执行特定的更新操作。
## 执行阶段
### `es`
执行级,根据译码得到的操作码,送往`alu``mul``div`进行特定类型的运算。`alu`用于处理较简单的运算,比如加减、移位运算。`mul`为华莱士树实现的乘法部件,拆为两级。`div`为常规的迭代除法部件需要34个周期得到结果。此外若为访存指令执行级便可通过加减运算得到访存地址。同`pfs`等待`dcache`准备就绪,即`data_addr_ok`置起,`es`便可发出`data_fetch`以及`data_addr`,由`tlb`进行虚实地址映射以及`dcache`取回数据。不过需要注意若当前指令或者后续流水线中的指令存在例外,需要及时中止`data_fetch`的置起。
## 访存阶段
### `dcache`
数据缓存,在`icache`的基础上新增了处理写请求的能力。每个`cache`行新增一个`dirty`位,用于标识该`cache`行是否为脏,在被替换时判断是否需要写回。此外,写请求在命中的情况下,相较于读请求,需要额外的一拍将数据写入到`ram`中。这额外一拍的延迟可以通过构建写请求状态机节省。待确认写请求命中后,进入状态机,使得`dcache`可以继续接收新的请求。不过需要注意规避读写请求同时出现在单端口的`ram`上。
### `ms`
访存级,等待`dcache`数据返回,由`data_data_ok`指示`data_rdata`是否有效。`ms`级同`fs`级,需要由`tlb_excp_cancel_req`信号取消掉上一拍`es`发往`dcache`的访存请求。写请求不同于读请求需要等待接收数据,待请求发出后,不管命不命中,可继续向后流动。
## 写回阶段
### `ws`
写回级,此时指令已执行完毕,并得到`result`,需根据执行结果修改处理器状态,包括两个方面:写通用寄存器`regfile`以及状态寄存器`csr`;根据指令触发的例外类型,修改`csr`寄存器,以及清空流水线并调整取指方向至例外入口。
---
## 流水级间交互
最理想的情况下同一时刻五个阶段都处于工作的状态也就表示同一时刻在处理五条指令。此时处理器核在通过流水线切分得到较高频率时带宽仍然保持为1指令/拍。但在实际情况下,如前文所述,并不会如此,各级流水总是会出现阻塞的情况。因此,流水级之间需要交互,避免一级流水的操作还未完成便被上一级覆盖。
流水级之间的交互逻辑中,需要维护以下关键信号:
- `stage_valid`:由触发器维护,表示当前流水级是否在处理指令。
- `stage_ready_go`:表示流水级是否需要被阻塞。
- `stage_allowin`:表示当前流水级是否允许上一级流水进入,该信号传递至上一级,与上一级流水交互,两种情况下该信号置起,当前流水无正在处理的指令;或者当前流水级中的指令已处理完毕且下一级流水允许进入。该信号会由最后一级流水向前传递,只要某一流水级阻塞,其`stage_allowin`拉低,同一时刻,前面所有流水级的`stage_allowin`都会拉低(假设所有流水级中都有指令在处理)。
- `stage_to_nextstage_valid`:表示当前流水级中是否有处理完毕的指令,该信号传递至下一级,与下一级流水交互。下一级流水在收到高电平信号时,且其下一级流水的`stage_allowin`为高电平,则会在下一拍置起下一级流水的`stage_valid`,实现流水级间指令的流动。`stage_to_nextstage_bus`总线信号中传递缓存信号,会随着`stage_to_nextstage_valid`流动。