共通 - SoC 设计
SoC 即 System on Chip,片上系统,一般包括中央处理器、存储器和外围电路等。
在本实验中,除了 CPU 的设计之外,也需要设计 CPU 周围的外设控制电路,用来操作 SRAM 和 UART。
地址映射
对于每个外设,我们都需要分配对应的地址用来访问这些外设,从而让外设正常工作。
地址分配方案如下:
外设名称 | 起始地址 | 结束地址 | 空间大小 | 地址掩码 |
---|---|---|---|---|
BaseRAM | 0x80000000 | 0x803FFFFF | 4 MB | 0xFFC00000 |
ExtRAM | 0x80400000 | 0x807FFFFF | 4 MB | 0xFFC00000 |
UART | 0x10000000 | 0x1000FFFF | 16 KB | 0xFFFF0000 |
地址掩码的含义与子网掩码类似:当 addr & mask == start_addr
时,我们认为 addr
属于该外设。
一般在规定完地址分配之后,会在软件上配置对应的外设和地址,从而完成软件和硬件之间地址的统一。但由于监控程序较为简易,无法改变其地址映射,因此需要让 SoC 的设计去适配监控程序的地址空间(即上表)。
如果同学们希望让自己的 CPU 支持更多的外设(如 VGA 对应的显存),则需要给新的外设分配对应大小的地址空间。
外设控制电路设计
请先回顾一下在 实验 5 # Wishbone Master 实现 一节中有关 wb_mux
的内容,再来阅读下面的说明。
我们回顾一下在 lab5/lab5_top.sv
中提供给大家的模板代码:
/* =========== Lab5 MUX begin =========== */
// Wishbone MUX (Masters) => bus slaves
...
wb_mux_3 wb_mux (
.clk(sys_clk),
.rst(sys_rst),
// Master interface (to Lab5 master)
.wbm_adr_i(wbm_adr_o),
.wbm_dat_i(wbm_dat_o),
.wbm_dat_o(wbm_dat_i),
.wbm_we_i (wbm_we_o),
.wbm_sel_i(wbm_sel_o),
.wbm_stb_i(wbm_stb_o),
.wbm_ack_o(wbm_ack_i),
.wbm_err_o(),
.wbm_rty_o(),
.wbm_cyc_i(wbm_cyc_o),
// Slave interface 0 (to BaseRAM controller)
// Address range: 0x8000_0000 ~ 0x803F_FFFF
.wbs0_addr (32'h8000_0000),
.wbs0_addr_msk(32'hFFC0_0000),
.wbs0_adr_o(wbs0_adr_o),
.wbs0_dat_i(wbs0_dat_i),
.wbs0_dat_o(wbs0_dat_o),
.wbs0_we_o (wbs0_we_o),
.wbs0_sel_o(wbs0_sel_o),
.wbs0_stb_o(wbs0_stb_o),
.wbs0_ack_i(wbs0_ack_i),
.wbs0_err_i('0),
.wbs0_rty_i('0),
.wbs0_cyc_o(wbs0_cyc_o),
// Slave interface 1 (to ExtRAM controller)
// Address range: 0x8040_0000 ~ 0x807F_FFFF
.wbs1_addr (32'h8040_0000),
.wbs1_addr_msk(32'hFFC0_0000),
.wbs1_adr_o(wbs1_adr_o),
.wbs1_dat_i(wbs1_dat_i),
.wbs1_dat_o(wbs1_dat_o),
.wbs1_we_o (wbs1_we_o),
.wbs1_sel_o(wbs1_sel_o),
.wbs1_stb_o(wbs1_stb_o),
.wbs1_ack_i(wbs1_ack_i),
.wbs1_err_i('0),
.wbs1_rty_i('0),
.wbs1_cyc_o(wbs1_cyc_o),
// Slave interface 2 (to UART controller)
// Address range: 0x1000_0000 ~ 0x1000_FFFF
.wbs2_addr (32'h1000_0000),
.wbs2_addr_msk(32'hFFFF_0000),
.wbs2_adr_o(wbs2_adr_o),
.wbs2_dat_i(wbs2_dat_i),
.wbs2_dat_o(wbs2_dat_o),
.wbs2_we_o (wbs2_we_o),
.wbs2_sel_o(wbs2_sel_o),
.wbs2_stb_o(wbs2_stb_o),
.wbs2_ack_i(wbs2_ack_i),
.wbs2_err_i('0),
.wbs2_rty_i('0),
.wbs2_cyc_o(wbs2_cyc_o)
);
/* =========== Lab5 MUX end =========== */
可以看到这里例化了一个 wb_mux_3
, 分别规定了 3 个 slave 端口的起始地址和地址掩码,与上表中的每一项一一对应。
如果需要增加外设,则要增加 wb_mux 中 slave 端口的数量。这时候则需要使用到wb_mux.py
。该 Python 脚本可以生成任意 slave 端口数量的 wb_mux 的 verilog 代码。
使用例子:如需生成 3 个 slave 端口的 wb_mux,则需执行如下命令:python wb_mux.py -p 3
, 执行后在文件夹中就可以看到生成的 verilog 代码 wb_mux_3.v
了。将其加入到工程中即可使用。
如果实现多周期 CPU,则只需要一个 wishbone master 端口,3 个 wishbone slave 端口的外设控制电路结构即可,这与实验 5 中的结构相同,因此可以直接使用实验 5 中的代码,结构如下:
flowchart LR
cpu_master --> wb_mux;
wb_mux --> sram_controller_base;
wb_mux --> sram_controller_ext;
wb_mux --> uart_controller;
若要实现流水线 CPU,则需要两个 wishbone master 端口,3 个 wishbone slave 端口的外设控制电路。此时需要使用到上一节中提供的双口 wishbone 仲裁器,最终的外围电路结构如下:
flowchart LR
cpu_if_master --> arbiter;
cpu_mem_master --> arbiter;
arbiter --> wb_mux;
wb_mux --> sram_controller_base;
wb_mux --> sram_controller_ext;
wb_mux --> uart_controller;
下面是另一种连接方法,其占用的资源更多,但是支持 if 和 mem 同时请求不同的 slave:
flowchart LR
cpu_if_master --> wb_mux_if;
cpu_mem_master --> wb_mux_mem;
wb_mux_if --> arbiter_sram_base;
wb_mux_if --> arbiter_sram_ext;
wb_mux_if --> arbiter_uart;
wb_mux_mem --> arbiter_sram_base;
wb_mux_mem --> arbiter_sram_ext;
wb_mux_mem --> arbiter_uart;
arbiter_sram_base --> sram_controller_base;
arbiter_sram_ext --> sram_controller_ext;
arbiter_uart --> uart_controller;
这需要在 lab5/lab5_top.sv
的基础上进行一定修改。