跳转至

共通 - 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 的基础上进行一定修改。


最后更新: 2024年9月18日
作者:Jiajie Chen (98.67%), cuibst (1.33%)