跳转至

共通 - SoC 设计

SoC 即 System on Chip,片上系统,一般包括中央处理器、存储器和外围电路等。

在本实验中,除了 CPU 的设计之外,也需要设计 CPU 周围的外设控制电路,用来操作 SRAM 和 UART。

CPU 负责根据指令发起访问,SRAM 和 UART 控制器负责把总线请求转换为芯片接口时序。可以复用实验 3、4 中的控制器与总线连接方法,将原来按固定流程运行的 Master 替换为 CPU 驱动的 Master。

地址映射

CPU 通过地址区分内存和外设。内存映射 I/O(MMIO)使 CPU 可以用同样的读写指令访问 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 对应的显存),则需要给新的外设分配对应大小的地址空间。

外设控制电路设计

请先回顾一下在 实验 4 # Wishbone Master 实现 一节中有关 wb_mux 的内容,再来阅读下面的说明。

我们回顾一下在 lab4/lab4_top.sv 中提供给大家的模板代码:

  /* =========== Lab4 MUX begin =========== */
  // Wishbone MUX (Masters) => bus slaves
  ...

  wb_mux_3 wb_mux (
      .clk(sys_clk),
      .rst(sys_rst),

      // Master interface (to Lab4 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)
  );

  /* =========== Lab4 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 端口的外设控制电路结构即可,这与实验 4 中的结构相同,因此可以直接使用实验 4 中的代码,结构如下:

flowchart LR
  cpu_master --> wb_mux;
  wb_mux --> sram_controller_base;
  wb_mux --> sram_controller_ext;
  wb_mux --> uart_controller;

若要实现流水线 CPU,IF 和 MEM 分别是两个 Wishbone Master,则需要两个 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;

这需要在 lab4/lab4_top.sv 的基础上进行一定修改。

总结

本节把 CPU、总线和外设控制器连接成完整系统。地址映射决定访问哪个设备,仲裁器决定哪个 Master 先访问。CPU 发出的是统一的总线请求,具体的 SRAM 和 UART 访问由各自控制器完成。

学完这节内容我需要做什么?

根据自己的 CPU 结构完成总线连线,确保取指、保存结果和串口输出都能到达正确的控制器。

作者:cuibst (73.17%), Jiajie Chen (12.8%), 于越洋 (10.37%), 张英奇 (3.66%)