1. 程式人生 > >自己動手寫cpu 讀書筆記

自己動手寫cpu 讀書筆記

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

本文來自《自己動手寫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呢?





           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述