# 设计概述
![系统框图](./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`流动。
