自己動手寫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呢?