《自己动手写cpu》读书笔记
本文来自《自己动手写cpu》一书的总结。原来自己看过原作者的《步步惊芯--软核处理器分析》以及其他关于or1200的书。本次粗略浏览了该书,就某些感兴趣的部分详细分析,并总结成此文。
关于5级流水的架构,可以自己去参考《计算机接口》一书。本文重点不在此。
1、如何从rom里面取地址
简化版的最基本的sopc的框图如下:
module openmips(
input wire clk,
input wire rst,
input wire[`RegBus] rom_data_i,
output wire[`RegBus] rom_addr_o,
output wire rom_ce_o
);
always @ (posedge clk) begin
if (ce == `ChipDisable) begin
pc <= 32'h00000000;
end else begin
pc <= pc + 4'h4;
end
end
always @ (posedge clk) begin
if (rst == `RstEnable) begin
ce <= `ChipDisable;
end else begin
ce <= `ChipEnable;
end
end
input wire[`InstAddrBus] if_pc,
input wire[`InstBus] if_inst,
output reg[`InstAddrBus] id_pc,
output reg[`InstBus] id_inst
always @ (posedge clk) begin
if (rst == `RstEnable) begin
id_pc <= `ZeroWord;
id_inst <= `ZeroWord;
end else begin
id_pc <= if_pc;
id_inst <= if_inst;
end
end
可以看到以上3个代码段,第一段是openmips中的,表示怎么连接含有指令的rom,第二段是pc_reg.v中的,pc是自加的,然后一直是可以对rom取址的,第3段是if_id.v中的,可以看到取址后传递给了译码模块。这就是最基本的程序跑起来的开始。
后面作者自己收东西写指令码实现了与寄存器s0的操作,按照$readmemh的要求写成 inst_rom.data。inst_rom.v中读入了该文件。可以参考$readmemh的语法。
2、协处理器的概念:
比如说这里的定时器中断时间就应该很有用。
3、异常相关指令的实现
这里的定时器中断设置,是自己去写的汇编代码,然后验证指令正确。实际上我们一般是用c语言去写程序,编译器生成汇编代码和机器码。所以这些异常指令实际上应该是编译器去生产的,异常程序入口地址也不是我们操心的。但是这里我们只能自己写了。
4、wishbone总线:
说明:根据上面那个图,可以看到哈佛结构的指令rom和数据ram,都是例化了wishbone接口,均采用点对点的方式,连接到外部的有同样wishbone接口的rom和ram中。由于只是采用了这种接口协议,针对具体的情况还得在中间再加上一层接口模块,即上图所示的状态机,一部分是控制状态转化的时序电路,另一部分是给处理器接口信号赋值的组合电路。
module openmips(
input wire clk,
input wire rst,
input wire[5:0] int_i,
//指令wishbone总线
input wire[`RegBus] iwishbone_data_i,
input wire iwishbone_ack_i,
output wire[`RegBus] iwishbone_addr_o,
output wire[`RegBus] iwishbone_data_o,
output wire iwishbone_we_o,
output wire[3:0] iwishbone_sel_o,
output wire iwishbone_stb_o,
output wire iwishbone_cyc_o,
//数据wishbone总线
input wire[`RegBus] dwishbone_data_i,
input wire dwishbone_ack_i,
output wire[`RegBus] dwishbone_addr_o,
output wire[`RegBus] dwishbone_data_o,
output wire dwishbone_we_o,
output wire[3:0] dwishbone_sel_o,
output wire dwishbone_stb_o,
output wire dwishbone_cyc_o,
output wire timer_int_o
);
wishbone_bus_if dwishbone_bus_if(
.clk(clk),
.rst(rst),
//来自控制模块ctrl
.stall_i(stall),
.flush_i(flush),
//CPU侧读写操作信息
.cpu_ce_i(ram_ce_o),
.cpu_data_i(ram_data_o),
.cpu_addr_i(ram_addr_o),
.cpu_we_i(ram_we_o),
.cpu_sel_i(ram_sel_o),
.cpu_data_o(ram_data_i),
//Wishbone总线侧接口
.wishbone_data_i(dwishbone_data_i),
.wishbone_ack_i(dwishbone_ack_i),
.wishbone_addr_o(dwishbone_addr_o),
.wishbone_data_o(dwishbone_data_o),
.wishbone_we_o(dwishbone_we_o),
.wishbone_sel_o(dwishbone_sel_o),
.wishbone_stb_o(dwishbone_stb_o),
.wishbone_cyc_o(dwishbone_cyc_o),
.stallreq(stallreq_from_mem)
);
wishbone_bus_if iwishbone_bus_if(
.clk(clk),
.rst(rst),
//来自控制模块ctrl
.stall_i(stall),
.flush_i(flush),
//CPU侧读写操作信息
.cpu_ce_i(rom_ce),
.cpu_data_i(32'h00000000),
.cpu_addr_i(pc),
.cpu_we_i(1'b0),
.cpu_sel_i(4'b1111),
.cpu_data_o(inst_i),
//Wishbone总线侧接口
.wishbone_data_i(iwishbone_data_i),
.wishbone_ack_i(iwishbone_ack_i),
.wishbone_addr_o(iwishbone_addr_o),
.wishbone_data_o(iwishbone_data_o),
.wishbone_we_o(iwishbone_we_o),
.wishbone_sel_o(iwishbone_sel_o),
.wishbone_stb_o(iwishbone_stb_o),
.wishbone_cyc_o(iwishbone_cyc_o),
.stallreq(stallreq_from_if)
);
该章节源代码并没有给出有wishbone接口的rom和ram的源代码,但是从第一段的代码中可以看到实际上是要接有这么一种接口的rom和ram的。第二段代码可以看到分别实现了指令和数据的wb接口控制模块,直接接到外面的ram和rom。
5、小型的SOPC
module openmips_min_sopc(
input wire clk,
input wire rst,
//新增uart接口
input wire uart_in,
output wire uart_out,
//16位GPIO输入接口
input wire[15:0] gpio_i,
</pre><pre code_snippet_id="1671506" snippet_file_name="blog_20160504_9_3260604" name="code" class="plain"> //32位GPIO输出接口
output wire[31:0] gpio_o,
//flash接口
input wire[7:0] flash_data_i,
output wire[21:0] flash_addr_o,
output wire flash_we_o,
output wire flash_rst_o,
output wire flash_oe_o,
output wire flash_ce_o,
<span style="font-family: Arial, Helvetica, sans-serif;"> //sdrsm接口</span>
output wire sdr_clk_o,
output wire sdr_cs_n_o,
output wire sdr_cke_o,
output wire sdr_ras_n_o,
output wire sdr_cas_n_o,
output wire sdr_we_n_o,
output wire[1:0] sdr_dqm_o,
output wire[1:0] sdr_ba_o,
output wire[12:0] sdr_addr_o,
inout wire[15:0] sdr_dq_io
);
openmips openmips0(
.clk(clk),
.rst(rst),
// 指令wb总线接口连到wb总线互联矩阵的主设备接口1
.iwishbone_data_i(m1_data_o),
.iwishbone_ack_i(m1_ack_o),
.iwishbone_addr_o(m1_addr_i),
.iwishbone_data_o(m1_data_i),
.iwishbone_we_o(m1_we_i),
.iwishbone_sel_o(m1_sel_i),
.iwishbone_stb_o(m1_stb_i),
.iwishbone_cyc_o(m1_cyc_i),
.int_i(int),
// 数据wb总线接口连到wb总线互联矩阵的主设备接口0
.dwishbone_data_i(m0_data_o),
.dwishbone_ack_i(m0_ack_o),
.dwishbone_addr_o(m0_addr_i),
.dwishbone_data_o(m0_data_i),
.dwishbone_we_o(m0_we_i),
.dwishbone_sel_o(m0_sel_i),
.dwishbone_stb_o(m0_stb_i),
.dwishbone_cyc_o(m0_cyc_i),
.timer_int_o(timer_int)
);
// GPIO连到wb总线互联矩阵的从设备接口2
gpio_top gpio_top0(
.wb_clk_i(clk),
.wb_rst_i(rst),
.wb_cyc_i(s2_cyc_o),
.wb_adr_i(s2_addr_o[7:0]),
.wb_dat_i(s2_data_o),
.wb_sel_i(s2_sel_o),
.wb_we_i(s2_we_o),
.wb_stb_i(s2_stb_o),
.wb_dat_o(s2_data_i),
.wb_ack_o(s2_ack_i),
.wb_err_o(),
.wb_inta_o(gpio_int),
.ext_pad_i(gpio_i_temp),
.ext_pad_o(gpio_o),
.ext_padoe_o()
);
// fiash控制器连到wb总线互联矩阵的从设备接口3
flash_top flash_top0(
.wb_clk_i(clk),
.wb_rst_i(rst),
.wb_adr_i(s3_addr_o),
.wb_dat_o(s3_data_i),
.wb_dat_i(s3_data_o),
.wb_sel_i(s3_sel_o),
.wb_we_i(s3_we_o),
.wb_stb_i(s3_stb_o),
.wb_cyc_i(s3_cyc_o),
.wb_ack_o(s3_ack_i),
//与小型sopc外部接口相连,对外是flash芯片
.flash_adr_o(flash_addr_o),
.flash_dat_i(flash_data_i),
.flash_rst(flash_rst_o),
.flash_oe(flash_oe_o),
.flash_ce(flash_ce_o),
.flash_we(flash_we_o)
);
// uart控制器连到wb总线互联矩阵的从设备接口1
uart_top uart_top0(
.wb_clk_i(clk),
.wb_rst_i(rst),
.wb_adr_i(s1_addr_o[4:0]),
.wb_dat_i(s1_data_o),
.wb_dat_o(s1_data_i),
.wb_we_i(s1_we_o),
.wb_stb_i(s1_stb_o),
.wb_cyc_i(s1_cyc_o),
.wb_ack_o(s1_ack_i),
.wb_sel_i(s1_sel_o),
.int_o(uart_int),
//连接uart接口
.stx_pad_o(uart_out),
.srx_pad_i(uart_in),
.cts_pad_i(1'b0),
.dsr_pad_i(1'b0),
.ri_pad_i(1'b0),
.dcd_pad_i(1'b0),
.rts_pad_o(),
.dtr_pad_o()
);
// sdram控制器连到wb总线互联矩阵的从设备接口0
sdrc_top sdrc_top0(
.cfg_sdr_width(2'b01),
.cfg_colbits(2'b00),
.wb_rst_i(rst),
.wb_clk_i(clk),
.wb_stb_i(s0_stb_o),
.wb_ack_o(s0_ack_i),
.wb_addr_i({s0_addr_o[25:2],2'b00}),
.wb_we_i(s0_we_o),
.wb_dat_i(s0_data_o),
.wb_sel_i(s0_sel_o),
.wb_dat_o(s0_data_i),
.wb_cyc_i(s0_cyc_o),
.wb_cti_i(3'b000),
//连接sdram
.sdram_clk(clk),
.sdram_resetn(~rst),
.sdr_cs_n(sdr_cs_n_o),
.sdr_cke(sdr_cke_o),
.sdr_ras_n(sdr_ras_n_o),
.sdr_cas_n(sdr_cas_n_o),
.sdr_we_n(sdr_we_n_o),
.sdr_dqm(sdr_dqm_o),
.sdr_ba(sdr_ba_o),
.sdr_addr(sdr_addr_o),
.sdr_dq(sdr_dq_io),
//Parameters
.sdr_init_done(sdram_init_done),
.cfg_req_depth(2'b11),
.cfg_sdr_en(1'b1),
.cfg_sdr_mode_reg(13'b0000000110001),
.cfg_sdr_tras_d(4'b1000),
.cfg_sdr_trp_d(4'b0010),
.cfg_sdr_trcd_d(4'b0010),
.cfg_sdr_cas(3'b100),
.cfg_sdr_trcar_d(4'b1010),
.cfg_sdr_twr_d(4'b0010),
.cfg_sdr_rfsh(12'b011010011000),
.cfg_sdr_rfmax(3'b100)
);
wb_conmax_top wb_conmax_top0(
.clk_i(clk),
.rst_i(rst),
// Master 0 Interface,数据接口
.m0_data_i(m0_data_i),
.m0_data_o(m0_data_o),
.m0_addr_i(m0_addr_i),
.m0_sel_i(m0_sel_i),
.m0_we_i(m0_we_i),
.m0_cyc_i(m0_cyc_i),
.m0_stb_i(m0_stb_i),
.m0_ack_o(m0_ack_o),
// Master 1 Interface,指令接口
.m1_data_i(m1_data_i),
.m1_data_o(m1_data_o),
.m1_addr_i(m1_addr_i),
.m1_sel_i(m1_sel_i),
.m1_we_i(m1_we_i),
.m1_cyc_i(m1_cyc_i),
.m1_stb_i(m1_stb_i),
.m1_ack_o(m1_ack_o),
// Slave 0 Interface,sdram控制器
.s0_data_i(s0_data_i),
.s0_data_o(s0_data_o),
.s0_addr_o(s0_addr_o),
.s0_sel_o(s0_sel_o),
.s0_we_o(s0_we_o),
.s0_cyc_o(s0_cyc_o),
.s0_stb_o(s0_stb_o),
.s0_ack_i(s0_ack_i),
.s0_err_i(1'b0),
.s0_rty_i(1'b0),
// Slave 1 Interface,uart控制器
.s1_data_i(s1_data_i),
.s1_data_o(s1_data_o),
.s1_addr_o(s1_addr_o),
.s1_sel_o(s1_sel_o),
.s1_we_o(s1_we_o),
.s1_cyc_o(s1_cyc_o),
.s1_stb_o(s1_stb_o),
.s1_ack_i(s1_ack_i),
.s1_err_i(1'b0),
.s1_rty_i(1'b0),
// Slave 2 Interface,gpio接口
.s2_data_i(s2_data_i),
.s2_data_o(s2_data_o),
.s2_addr_o(s2_addr_o),
.s2_sel_o(s2_sel_o),
.s2_we_o(s2_we_o),
.s2_cyc_o(s2_cyc_o),
.s2_stb_o(s2_stb_o),
.s2_ack_i(s2_ack_i),
.s2_err_i(1'b0),
.s2_rty_i(1'b0),
// Slave 3 Interface,flash控制器
.s3_data_i(s3_data_i),
.s3_data_o(s3_data_o),
.s3_addr_o(s3_addr_o),
.s3_sel_o(s3_sel_o),
.s3_we_o(s3_we_o),
.s3_cyc_o(s3_cyc_o),
.s3_stb_o(s3_stb_o),
.s3_ack_i(s3_ack_i),
.s3_err_i(1'b0),
.s3_rty_i(1'b0),
);
endmodule
从上面可以看到是怎么集成进去的。全书写了几个外设模块的原理,但是一个关键的wb_conmax的实现原理,其实现了总线的上述诸多功能,包括仲裁等。这里只是直接拿来用了,理清这些关系很重要。
为什么flash的地址是0x30000000开始,因为我们是把他接在从设备3了。wb_conmax有规定,根据从设备来设定地址区段。
这些外置的接口ip都是通过mcu操纵其ip内部的寄存器来实现的。比如gpio:
所以要知道外部io口上的数据,只需内核去读这个地址的RGPIO_IN的这个寄存器就可以了。
所以我们实际上理解的编译器,实际上只是将我们的c语言代码编译成最核心的流水线内核的操作指令,涉及到外设的部分仅仅是生成一些操作寄存器的指令,即编译器只是理解外设与寄存器一致,只需生成访存和写回的指令即可。
6、测试与验证
在上面实现后怎么验证,作者用de2的开发板:
由于与mips指令兼容,所以可以使用mips的编译器,生成了inst_rom.o的可重定位elf文件,通过ld文件生成可执行文件,但是有elf文件头,与我们期望的格式还是有很大差别,可以利用mips-sde-elf-objcopy得到.bin的纯二进制格式,这正是我们需要的。(如果是用modelsim仿真,还需将其生成modelsim中存储器初始化文件的格式生成.data文件)
de2中将利用控制面板程序擦写进flash。
注意在编译前要修改ram.ld文件,将其中的起始地址从0x00000000修改为0x30000000,因为前2个测试陈旭是在flash中运行的,而flash的起始地址是0x30000000.
例子3模拟了os的启动过程:
后面是应用程序:
怎么写入flash呢?