首页 > 代码库 > 基于OR1200的一个简单SOPC
基于OR1200的一个简单SOPC
以下内容摘自《步步惊芯——软核处理器内部设计分析》一书
在本书第2章建立了最小系统,最小系统只由CPU、QMEM模块组成,借助于最小系统,我们分析了OR1200各类指令的执行过程、熟悉了流水线的工作原理以及CPU内部各个模块的代码实现,第10章在最小系统上增添了IMMU、DMMU模块,借此分析了OR1200中内存管理单元的实现原理。本章将建立一个基于OR1200的简单SOPC,后面的示例程序将运行在这个简单SOPC之上,借助于该SOPC分析OR1200中ICache、DCache、Wishbone总线接口单元等模块。
简单SOPC由OR1200、互联矩阵WB_CONMAX、RAM组成,11.1节给出了简单SOPC的结构,11.2、11.3节分别介绍了互联矩阵WB_CONMAX、RAM,11.4节介绍了SOPC的顶层文件。有了前面的准备, 11.5节新建ModelSim工程min_or1200_sopc用来仿真简单SOPC。本章最后编写了一个示例程序,并使用ModelSim仿真观察其在简单SOPC上的执行效果。
11.1 简单SOPC的结构
第3章介绍Wishbone总线的时候提及Wishbone总线有四种互联方式:点对点、数据流、共享总线、交叉互联。
点对点方式在OR1200内部广泛使用,一般有一个主设备、一个从设备,比如CPU与IMMU、QMEM之间,CPU与DMMU、QMEM之间,QMEM与ICache之间,QMEM与Store Buffer(SB)之间,SB与DCache之间等,在分析QMEM模块时都已介绍,读者可以参考图3.7-3.12。
但是对于一个片上系统,存在多个模块,并且某一模块能够访问其余多个模块,比如存在CPU、DMA控制器、Flash、RAM、GPIO等,其中CPU、DMA控制器作为主设备,Flash、RAM、GPIO作为从设备,主设备CPU可以访问Flash、RAM、GPIO,主设备DMA控制器也可以访问Flash、RAM、GPIO,当两者对同一设备发出访问请求时,需要一个仲裁机制判断哪个主设备占用总线,所以片上系统一般使用共享总线或者交叉互联方式。
1、共享总线
共享总线互联方式适合于系统中有两个或者多个主设备需要与一个或者多个从设备通信的情况,它们通过共享的总线进行通信。主设备在需要与一个从设备通信时,需要先向仲裁器申请总线占有权,获得允许后开始占用总线并与目标从设备开始通信,通信结束后释放总线。当多个主设备同时希望占有总线时,仲裁器通过一定的优先级逻辑分配总线使用机会。其典型框图如图11.1所示。共享总线的缺点是同一时刻只能有一对主、从设备建立通信。
2、交叉互联
交叉互联主要使用在多个主设备同时访问多个从设备的情况,其典型框图如图11.2所示。在这种连接方式下,主设备发出地址总线请求对某个从设备进行访问,仲裁器查看总线和从设备是否空闲,从而决定是否给主设备总线访问权。交叉互联方式允许多对主设备和从设备同时进行通信,而共享总线互联方式在同一时刻只允许一对主、从设备进行通信。
本章建立的简单SOPC使用的就是交叉互联方式,其结构如图11.3所示。在Wishbone总线上挂接了两个模块:OR1200、RAM。其中Wishbone总线使用的是OpenCores站点提供的开源项目WB_CONMAX,这是一个Wishbone总线互联矩阵,允许多对主从设备同时相互通信。在简单SOPC中只有两个模块挂接在Wishbone总线上,按理说这么简单的系统完全可以不使用Wishbone总线互联矩阵WB_CONMAX,直接将OR1200与RAM连接即可,但是笔者使用了WB_CONMAX,目的是可以方便的扩展简单SOPC,比如:读者可以在WB_CONMAX上挂接UART、GPIO、Flash、SD Controller等功能单元。
11.2 Wishbone总线互联矩阵WB_CONMAX
读者使用SVN从http://opencores.org/ocsvn/wb_conmax/wb_conmax可以CheckOut最新的WB_CONMAX代码,下载前需要首先在OpenCores站点注册,下载的时候输入在OpenCores站点注册时的用户名和密码。WB_CONMAX模块有如下特点:
- 支持8个Wishbone总线主设备
- 支持16个Wishbone总线从设备
- 内置仲裁器,支持1、2或4个优先级
- 允许多对主从设备同时相互通信
- 支持Wishbone B2版本
WB_CONMAX模块的结构如图11.4所示。主设备选择从设备进行通信时,依据主设备提供的Wishbone地址的高4位确定是选择哪一个从设备进行通信,当地址高4位为0时,选择的就是从设备0,当地址高4位为15时,选择的就是从设备15。所以每个从设备的寻址空间大小都是250M,其中从设备0的寻址空间是从0x0-0xfffffff。本章建立的简单SOPC中各个模块与WB_CONMAX的连接如图11.5所示。
OR1200具有分开的指令、数据接口,所以占用WB_CONMAX两个主设备接口,其中指令接口连接到主设备接口0,数据接口连接到主设备接口1。RAM连接到从设备接口0。
11.3 挂接在互联矩阵WB_CONMAX下的RAM模块
简单SOPC中的RAM模块与QMEM中的RAM一样都是使用数组实现的,文件名为ram.v,代码如下:
ram.vmodule ram ( rst, addr, sel, clk, di, we, doq);parameter aw = 13; //地址线的宽度是13parameter dw = 32; //数据线的宽度是32input clk; // 时钟input rst; // 复位input we; // 写使能输入input [aw-1:0] addr; // 输入地址input [dw-1:0] di; // 输入数据output [dw-1:0] doq; // 输出数据input [3:0] sel; // 要写入的有效字节reg [dw-1:0] mem [(1<<aw)-1:0]; // 使用数组存储RAM内容reg [aw-1:0] addr_reg; // 读RAM时使用的地址initial $readmemh ( "mem.data", mem ); //使用mem.data文件初始化RAMassign doq = mem[addr_reg]; // 读RAMalways @(posedge clk or `OR1200_RST_EVENT rst) // 锁存地址 if (rst == `OR1200_RST_VALUE) addr_reg <= {aw{1'b0}}; else addr_reg <= addr;always @(posedge clk) // 写RAM,此处的RAM可以写单个字节,参考QMEM中的RAM if (we) mem[addr] <= { sel[3] ? di[31:24] : mem[addr][31:24], sel[2] ? di[23:16] : mem[addr][23:16], sel[1] ? di[15:8] : mem[addr][15:8], sel[0] ? di[7:0] : mem[addr][7:0]};endmodule
为了将上面的RAM挂接到WB_CONMAX,还需要为其添加一层Wishbone总线接口,在文件ram_top.v中实现,代码如下:
//在ram_top.v中例化上面的ramram_top.vmodule ram_top( input clk_i, //时钟信号 input rst_i, //复位信号 input wb_stb_i, //Wishbone总线选通信号 input wb_cyc_i, //Wishbone总线周期信号 output reg wb_ack_o, //Wishbone总线操作结束信号 input [31:0] wb_addr_i, //地址信号 input [3:0] wb_sel_i, //Wishbone数据总线选择信号 input wb_we_i, //Wishbone总线写信号 input [31:0] wb_data_i, //要写入的数据 output [31:0] wb_data_o //读出的数据 ); wire request; wire [12:0] ram_address; wire [31:0] ram_data; wire [3:0] ram_byteena; wire ram_wren; reg request_delay; wire request_rising_edge; //request为1表示Wishbone总线操作周期开始 assign request = wb_stb_i & wb_cyc_i; //request为1时,总线操作周期开始,所以RAM的输入信号变为 //Wishbone总线上的信号,反之为0. assign ram_address = (request == 1'b1)? wb_addr_i[14:2]:13'b0; assign ram_data = http://www.mamicode.com/(request == 1'b1)? wb_data_i:32'b0;>
11.4 SOPC顶层文件
新建一个文件or1200_sopc.v,作为简单SOPC的顶层文件,在其中例化OR1200、WB_CONMAX、RAM等模块,主要代码如下:
or1200_sopc.vmodule or1200_sopc( input clk_i, input rst_i);……//例化OR1200or1200_top u_or1200( .clk_i(clk_i), .rst_i(rst_i), .pic_ints_i({18'b0,1'b0,1'b0}), .clmode_i(2'b00), // 指令Wishbone总线接口,连接到WB_CONMAX的主设备接口0 .iwb_clk_i(clk_i), .iwb_rst_i(rst_i), .iwb_ack_i(wire_iwb_ack_i), .iwb_err_i(wire_iwb_err_i), .iwb_rty_i(wire_iwb_rty_i), .iwb_dat_i(wire_iwb_data_i), .iwb_cyc_o(wire_iwb_cyc_o), .iwb_adr_o(wire_iwb_addr_o), .iwb_stb_o(wire_iwb_stb_o), .iwb_we_o(wire_iwb_we_o), .iwb_sel_o(wire_iwb_sel_o), .iwb_dat_o(wire_iwb_data_o), `ifdef OR1200_WB_CAB .iwb_cab_o(), `endif // 数据Wishbone总线接口,连接到WB_CONMAX的主设备接口1 .dwb_clk_i(clk_i), .dwb_rst_i(rst_i), .dwb_ack_i(wire_dwb_ack_i), .dwb_err_i(wire_dwb_err_i), .dwb_rty_i(wire_dwb_rty_i), .dwb_dat_i(wire_dwb_data_i), .dwb_cyc_o(wire_dwb_cyc_o), .dwb_adr_o(wire_dwb_addr_o), .dwb_stb_o(wire_dwb_stb_o), .dwb_we_o(wire_dwb_we_o), .dwb_sel_o(wire_dwb_sel_o), .dwb_dat_o(wire_dwb_data_o), `ifdef OR1200_WB_CAB .dwb_cab_o(), `endif // 调试接口,在简单SOPC中不考虑调试模块,所以此处的输入都直接为0, // 输出接口不用设置 .dbg_stall_i(1'b0), .dbg_ewt_i(1'b0), .dbg_lss_o(), .dbg_is_o(), .dbg_wp_o(), .dbg_bp_o(), .dbg_stb_i(1'b0), .dbg_we_i(1'b0), .dbg_adr_i(0), .dbg_dat_i(0), .dbg_dat_o(), .dbg_ack_o(), // 电源管理接口,在简单SOPC中不考虑电源管理,所以此处的输入都直接 // 为0,输出接口不用设置 .pm_cpustall_i(0), .pm_clksd_o(), .pm_dc_gate_o(), .pm_ic_gate_o(), .pm_dmmu_gate_o(), .pm_immu_gate_o(), .pm_tt_gate_o(), .pm_cpu_gate_o(), .pm_wakeup_o(), .pm_lvolt_o() ); // 例化WB_CONMAX wb_conmax_top u_wb( .clk_i(clk_i), .rst_i(rst_i), // 主设备接口0,连接到OR1200的指令Wishbone总线接口 .m0_data_i(wire_iwb_data_o), .m0_data_o(wire_iwb_data_i), .m0_addr_i(wire_iwb_addr_o), .m0_sel_i(wire_iwb_sel_o), .m0_we_i(wire_iwb_we_o), .m0_cyc_i(wire_iwb_cyc_o), .m0_stb_i(wire_iwb_stb_o), .m0_ack_o(wire_iwb_ack_i), .m0_err_o(wire_iwb_err_i), .m0_rty_o(wire_iwb_rty_i), // 主设备接口1,连接到OR1200的数据Wishbone总线接口 .m1_data_i(wire_dwb_data_o), .m1_data_o(wire_dwb_data_i), .m1_addr_i(wire_dwb_addr_o), .m1_sel_i(wire_dwb_sel_o), .m1_we_i(wire_dwb_we_o), .m1_cyc_i(wire_dwb_cyc_o), .m1_stb_i(wire_dwb_stb_o), .m1_ack_o(wire_dwb_ack_i), .m1_err_o(wire_dwb_err_i), .m1_rty_o(wire_dwb_rty_i), // 从设备接口0,连接到RAM .s0_data_i(wire_ram0_data_o), .s0_data_o(wire_ram0_data_i), .s0_addr_o(wire_ram0_addr_i), .s0_sel_o(wire_ram0_sel_i), .s0_we_o(wire_ram0_we_i), .s0_cyc_o(wire_ram0_cyc_i), .s0_stb_o(wire_ram0_stb_i), .s0_ack_i(wire_ram0_ack_o), .s0_err_i(0), .s0_rty_i(0) ); //例化RAM,连接到WB_CONMAX的从设备接口0 ram_top u_ram0( .clk_i(clk_i), .rst_i(rst_i), .wb_stb_i(wire_ram0_stb_i), .wb_cyc_i(wire_ram0_cyc_i), .wb_ack_o(wire_ram0_ack_o), .wb_addr_i(wire_ram0_addr_i), .wb_sel_i(wire_ram0_sel_i), .wb_we_i(wire_ram0_we_i), .wb_data_i(wire_ram0_data_i), .wb_data_o(wire_ram0_data_o) ); endmodule
顶层文件的代码使得OR1200挂接在WB_CONMAX的主设备接口0、1,RAM挂接在WB_CONMAX的从设备接口0,符合之前设计的简单SOPC的结构。
11.5 ModelSim新建工程min_or1200_sopc
使用ModelSim新建一个工程min_or1200_sopc,其中添加文件夹OR1200、Memory、wb_conmax,在OR1200文件夹下添加OR1200源代码,在Memory文件夹下添加上文的ram.v、ram_top.v两个文件,在wb_conmax文件夹下添加WB_CONMAX源代码,在工程根目录下添加SOPC顶层文件or1200_sopc.v,最终min_or1200_sopc工程的文件结构如图11.6所示。
其中工程根目录下的or1200_tb.v是测试(TestBench)文件,代码十分简单,如下:
or1200_tb.v`timescale 1ns/100psmodule or1200_tb(); reg CLOCK_50; //时钟信号 reg rst; //复位信号 //每10ns翻转一次,所以时钟频率是50MHz initial begin CLOCK_50 = 1'b0; forever #10 CLOCK_50 = ~CLOCK_50; end initial begin rst = 1'b0; #200 rst= 1'b1; //第200ns时开始复位 #100 rst= 1'b0; //复位信号持续100ns #4000 $stop; //仿真运行4000ns后结束 end or1200_sopc or1200_sopc_inst //例化or1200_sopc ( .clk_i(CLOCK_50), .rst_i(rst) );endmodule
但此时还不能运行仿真,因为OR1200的指令总线、数据总线默认是Wishbone B3版本,而WB_CONMAX只支持Wishbone B2版本,所以需要配置OR1200,配置的方法按理说应该很简单,只需在or1200_defines.v中注释掉OR1200_WB_B3即可,但是由于OR1200 rel3版本中Wishbone总线接口模块WB_BIU的代码有错误,使得注释掉宏定义OR1200_WB_B3后,ModelSim编译时会出错,为了解决这个问题,笔者采取的方法如下:
(1)不注释掉or1200_defines.v中的宏定义OR1200_WB_B3;
(2)修改OR1200中WB_BIU的代码,如下:
or1200_wb_biu.v always @(wb_fsm_state_cur or burst_len or wb_err_i or wb_rty_i or wb_ack or wb_cti_o or wb_sel_o or wb_stb_o or wb_we_o or biu_cyc_i or biu_stb or biu_cab_i or biu_sel_i or biu_we_i) begin case(wb_fsm_state_cur) wb_fsm_idle : begin wb_cyc_nxt = biu_cyc_i & biu_stb; wb_stb_nxt = biu_cyc_i & biu_stb; //原值为wb_cti_nxt = {!biu_cab_i, 1'b1, !biu_cab_i}; wb_cti_nxt = {1'b1, 1'b1, 1'b1}; ……
将wb_cti_nxt改为3`b111,也就是强制不使用突发模式,读者此时不需要明白为何如此修改,在后续分析WB_BIU模块时自然会明白。修改后就可以使用ModelSim成功编译、仿真。
11.6 示例程序
为了检验上一节建立的简单SOPC是否可以正常工作,本节将对其进行仿真验证。新建文件Example.S,内容如下:
.section .text,"ax" .global _start .org 0x100 _start: l.movhi r0,0x0 l.movhi r1,0x1 l.movhi r1,0x2 l.movhi r1,0x3 l.movhi r1,0x4 l.movhi r1,0x5 l.movhi r1,0x6 l.movhi r1,0x7 l.nop
代码是非常简单的,其执行效果就是寄存器r1的值从0x10000、0x20000、0x30000依次递增至0x70000。拷贝ram.ld、Makefile、Bin2Mem.exe到Example.S所在目录,此处的Makefile选择在10章中修改过后的Makefile,即不使用OR1KSim进行模拟。打开终端,调整路径到上述文件所在目录,输入“make all”得到可以在ModelSim仿真中使用的存储器初始化文件mem.data。简单SOPC就使用该文件初始化RAM。ModelSim仿真波形如图11.7所示。从仿真结果可知r1的变化是符合预期的,SOPC工作正常。本书光盘的Chapter11目录下包括ModelSim仿真工程,Chapter11/Code目录下包括示例程序源代码。
有几点说明:
(1)本章建立的简单SOPC中OR1200的源代码是没有修改过的源代码,不是第2章中修改的,所以QMEM的地址范围是默认值,为0x00800000-0x008fffff。
(2)OR1200读取的第一条指令地址是0x100,该地址位于从设备0的地址空间,即位于RAM中。
(3)通过第4章的分析可知,指令l.movhi的执行阶段只需要1个时钟周期,但是在图11.7中却发现指令l.movhi的执行阶段占用多个时钟周期,这是由于取指造成的,添加上WB_CONMAX模块后,取指需要多个时钟周期,从OR1200的角度观察如图11.8所示,if_stall为1表示由于取指导致处理器暂停,xx_freeze也为1使得流水线暂停,直到取得指令后icpu_ack_i为1,if_stall才恢复为0,xx_freeze也恢复为0表示流水线继续,这一过程类似于转移指令的执行过程。
第2章建立的最小系统由于将指令存储在处理器的QMEM中,而从QMEM中取指只需要一个时钟周期,所以我们的理想取指模型就是一个时钟周期一条指令,在之前的分析中也都没有考虑由于取指导致的处理器暂停,但是本章建立的SOPC将指令存储在处理器外的存储器上(RAM挂接在Wishbone总线上),这种情况也更接近实际,程序存放在Flash、硬盘上,都需要多个时钟周期取指,导致处理器性能下降,为此出现了Cache,用来存放最近使用过、即将使用到的指令,尽量减少由于取指导致的处理器性能下降,OR1200处理器中Cache的设计原理及其实现效果将在接下来的两章进行分析。