在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

RISC-V是什么 數(shù)字電路設(shè)計中的時序問題

嵌入式情報局 ? 來源:ZYNQ ? 作者:liangkangnan ? 2022-11-07 15:36 ? 次閱讀

緒論

RISC-V是什么

RISC,即精簡指令集處理器,是相對于X86這種CISC(復(fù)雜指令集處理器)來說的。RISC-V中的V是羅馬數(shù)字,也即阿拉伯數(shù)字中的5,就是指第5代RISC。

RISC-V是一種指令集架構(gòu),和ARM、MIPS這些是屬于同一類東西。RISC-V誕生于2010年,最大的特點是開源,任何人都可以設(shè)計RISC-V架構(gòu)的處理器并且不會有任何版權(quán)問題。

既生ARM,何生RISC-V

ARM是一種很優(yōu)秀的處理器,這一點是無可否認的,在RISC處理器中是處于絕對老大的地位。但是ARM是閉源的,要設(shè)計基于ARM的處理器是要交版權(quán)費的,或者說要購買ARM的授權(quán),而且這授權(quán)費用是昂貴的。

RISC-V的誕生并不是偶然的,而是必然的,為什么?且由我從以下兩大領(lǐng)域進行說明。

先看開源軟件領(lǐng)域(或者說是操作系統(tǒng)領(lǐng)域),Windows是閉源的,Linux是開源的,Linux有多成功、對開源軟件有多重要的意義,這個不用多說了吧。再看手機操作系統(tǒng)領(lǐng)域,iOS是閉源的,Android是開源的,Android有多成功,這個也不用多說了吧。對于RISC處理器領(lǐng)域,由于有了ARM的閉源,必然就會有另外一種開源的RISC處理器。RISC-V之于CPU的意義,就好比Linux之于開源軟件的意義。

或者你會說現(xiàn)在也有好多開源的處理器架構(gòu)啊,比如MIPS等等,為什么偏偏是RISC-V?這個在這里我就不細說了,我只想說一句:大部分人能看到的機遇不會是一個好的機遇,你懂的。

可以說未來十年乃至更長時間內(nèi)不會有比RISC-V更優(yōu)秀的開源處理器架構(gòu)出現(xiàn)。錯過RISC-V,你注定要錯過一個時代。

淺談Verilog

verilog,確切來說應(yīng)該是verilog HDL(Hardware Description Language ),從它的名字就可以知道這是一種硬件描述語言。首先它是一種語言,和C語言C++語言一樣是一種編程語言,那么verilog描述的是什么硬件呢?描述電阻?描述電容?描述運算放大器?都不是,它描述的是數(shù)字電路里的硬件,比如與、非門、觸發(fā)器、鎖存器等等。

既然是編程語言,那一定會有它的語法,學過C語言的同學再來看verilog得代碼,會發(fā)現(xiàn)有很多地方是相似的。

verilog的語法并不難,難的是什么時候該用wire類型,什么時候該用reg類型,什么時候該用assign來描述電路,什么時候該用always來描述電路。assign能描述組合邏輯電路,always也能描述組合邏輯電路,兩者有什么區(qū)別呢?

用always描述組合邏輯電路

我們知道數(shù)字電路里有兩大類型的電路,一種是組合邏輯電路,另外一種是時序邏輯電路。組合邏輯電路不需要時鐘作為觸發(fā)條件,因此輸入會立即(不考慮延時)反映到輸出。時序邏輯電路以時鐘作為觸發(fā)條件,時鐘的上升沿到來時輸入才會反映到輸出。

在verilog中,assign能描述組合邏輯電路,always也能描述組合邏輯電路。對于簡單的組合邏輯電路的話兩者描述起來都比較好懂、容易理解,但是一旦到了復(fù)雜的組合邏輯電路,如果用assign描述的話要么是一大串要么是要用好多個assign,不容易弄明白。但是用always描述起來卻是非常容易理解的。

既然這樣,那全部組合邏輯電路都用always來描述好了,呵呵,既然assign存在就有它的合理性。

用always描述組合邏輯電路時要注意避免產(chǎn)生鎖存器,if和case的分支情況要寫全。

tinyriscv中用了大量的always來描述組合邏輯電路,特別是在譯碼和執(zhí)行階段。

數(shù)字電路設(shè)計中的時序問題

要分析數(shù)字電路中的時序問題,就一定要提到以下這個模型。

58532970-5c60-11ed-a3b6-dac502259ad0.png

其中對時序影響最大的是上圖中的組合邏輯電路。所以要避免時序問題,最簡單的方法減小組合邏輯電路的延時。組合邏輯電路里的串聯(lián)級數(shù)越多延時就越大,實在沒辦法減小串聯(lián)級數(shù)時,可以采用流水線的方式將這些級數(shù)用觸發(fā)器隔開。

流水線設(shè)計

要設(shè)計處理器的話,流水線是繞不開的。當然你也可以抬杠說:”用狀態(tài)機也可以實現(xiàn)處理器啊,不一定要用流水線。”

采用流水線設(shè)計方式,不但可以提高處理器的工作頻率,還可以提高處理器的效率。但是流水線并不是越長越好,流水線越長要使用的資源就越多、面積就越大。

在設(shè)計一款處理器之前,首先要確定好所設(shè)計的處理器要達到什么樣的性能(或者說主頻最高是多少),所使用的資源的上限是多少,功耗范圍是多少。如果一味地追求性能而不考慮資源和功耗的話,那么所設(shè)計出來的處理器估計就只能用來玩玩,或者做做學術(shù)研究。

tinyriscv采用的是三級流水線,即取指、譯碼和執(zhí)行,設(shè)計的目標就是要對標ARM的Cortex-M3系列處理器。

代碼風格

代碼風格其實并沒有一種標準,但是并不代表代碼風格不重要。好的代碼風格可以讓別人看你的代碼時有一種賞心悅目的感覺。哪怕代碼只是寫給自己看,也一定要養(yǎng)成好的代碼風格的習慣。tinyriscv的代碼風格在很大程度上沿用了寫C語言代碼所采用的風格。

下面介紹tinyriscv的一些主要的代碼風格。

縮進

統(tǒng)一使用4個空格。

if語句

不管if語句下面有多少行語句,if下面的語句都由begin…end包起來,并且begin在if的最后,如下所示:

1if(a==1'b1)begin
2c<=?b;
3end?else?begin
4????c?<=?d;
5end

case語句

對于每一個分支情況,不管有多少行語句,都由begin…end包起來,如下所示:

1case(a)
2c:begin
3e=g;
4end
5default:begin
6b=t;
7end
8endcase

always語句

always語句后跟begin,如下所示:

1always@(posedgeclk)begin
2a<=?b;
3end

其他

=、==、<=、>=、+、-、*、/、@等符號左右各有一個空格。

,和:符號后面有一個空格。

對于模塊的輸入信號,不省略wire關(guān)鍵字。

每個文件的最后留一行空行。

if、case、always后面都有一個空格。

硬件篇

硬件篇主要介紹tinyriscv的verilog代碼設(shè)計。

tinyriscv整體框架如圖2_1所示。

5869ac4a-5c60-11ed-a3b6-dac502259ad0.png

圖2_1 tinyriscv整體框架

可見目前tinyriscv已經(jīng)不僅僅是一個內(nèi)核了,而是一個小型的SOC,包含一些簡單的外設(shè),如timer、uart_tx等。

tinyriscv SOC輸入輸出信號有兩部分,一部分是系統(tǒng)時鐘clk和復(fù)位信號rst,另一部分是JTAG調(diào)試信號,TCK、TMS、TDI和TDO。

上圖中的小方框表示一個個模塊,方框里面的文字表示模塊的名字,箭頭則表示模塊與模塊之間的的輸入輸出關(guān)系。

下面簡單介紹每個模塊的主要作用。

jtag_top:調(diào)試模塊的頂層模塊,主要有三大類型的信號,第一種是讀寫內(nèi)存的信號,第二種是讀寫寄存器的信號,第三種是控制信號,比如復(fù)位MCU,暫停MCU等。

pc_reg:PC寄存器模塊,用于產(chǎn)生PC寄存器的值,該值會被用作指令存儲器的地址信號。

if_id:取指到譯碼之間的模塊,用于將指令存儲器輸出的指令打一拍后送到譯碼模塊。

id:譯碼模塊,純組合邏輯電路,根據(jù)if_id模塊送進來的指令進行譯碼。當譯碼出具體的指令(比如add指令)后,產(chǎn)生是否寫寄存器信號,讀寄存器信號等。由于寄存器采用的是異步讀方式,因此只要送出讀寄存器信號后,會馬上得到對應(yīng)的寄存器數(shù)據(jù),這個數(shù)據(jù)會和寫寄存器信號一起送到id_ex模塊。

id_ex:譯碼到執(zhí)行之間的模塊,用于將是否寫寄存器的信號和寄存器數(shù)據(jù)打一拍后送到執(zhí)行模塊。

ex:執(zhí)行模塊,純組合邏輯電路,根據(jù)具體的指令進行相應(yīng)的操作,比如add指令就執(zhí)行加法操作等。此外,如果是lw等訪存指令的話,則會進行讀內(nèi)存操作,讀內(nèi)存也是采用異步讀方式。最后將是否需要寫寄存器、寫寄存器地址,寫寄存器數(shù)據(jù)信號送給regs模塊,將是否需要寫內(nèi)存、寫內(nèi)存地址、寫內(nèi)存數(shù)據(jù)信號送給rib總線,由總線來分配訪問的模塊。

div:除法模塊,采用試商法實現(xiàn),因此至少需要32個時鐘才能完成一次除法操作。

ctrl:控制模塊,產(chǎn)生暫停流水線、跳轉(zhuǎn)等控制信號。

clint:核心本地中斷模塊,對輸入的中斷請求信號進行總裁,產(chǎn)生最終的中斷信號。

rom:程序存儲器模塊,用于存儲程序(bin)文件。

ram:數(shù)據(jù)存儲器模塊,用于存儲程序中的數(shù)據(jù)。

timer:定時器模塊,用于計時和產(chǎn)生定時中斷信號。目前支持RTOS時需要用到該定時器。

uart_tx:串口發(fā)送模塊,主要用于調(diào)試打印。

gpio:簡單的IO口模塊,主要用于點燈調(diào)試。

spi:目前只有master角色,用于訪問spi從機,比如spi norflash。

PC寄存器

PC寄存器模塊所在的源文件:rtl/core/pc_reg.v

PC寄存器模塊的輸入輸出信號如下表所示:

589d7c5a-5c60-11ed-a3b6-dac502259ad0.png

PC寄存器模塊代碼比較簡單,直接貼出來:

 1always@(posedgeclk)begin
 2//復(fù)位
 3if(rst==`RstEnable||jtag_reset_flag_i==1'b1)begin
 4pc_o<=?`CpuResetAddr;
 5????//?跳轉(zhuǎn)
 6????end?else?if?(jump_flag_i?==?`JumpEnable)?begin
 7????????pc_o?<=?jump_addr_i;
 8????//?暫停
 9????end?else?if?(hold_flag_i?>=`Hold_Pc)begin
10pc_o<=?pc_o;
11????//?地址加4
12????end?else?begin
13????????pc_o?<=?pc_o?+?4'h4;
14????end
15end
16

第3行,PC寄存器的值恢復(fù)到原始值(復(fù)位后的值)有兩種方式,第一種不用說了,就是復(fù)位信號有效。第二種是收到j(luò)tag模塊發(fā)過來的復(fù)位信號。PC寄存器復(fù)位后的值為CpuResetAddr,即32’h0,可以通過改變CpuResetAddr的值來改變PC寄存器的復(fù)位值。

第6行,判斷跳轉(zhuǎn)標志是否有效,如果有效則直接將PC寄存器的值設(shè)置為jump_addr_i的值。因此可以知道,所謂的跳轉(zhuǎn)就是改變PC寄存器的值,從而使CPU從該跳轉(zhuǎn)地址開始取指。

第9行,判斷暫停標志是否大于等于Hold_Pc,該值為3’b001。如果是,則保持PC寄存器的值不變。這里可能會有疑問,為什么Hold_Pc的值不是一個1bit的信號。因為這個暫停標志還會被if_id和id_ex模塊使用,如果僅僅需要暫停PC寄存器的話,那么if_id模塊和id_ex模塊是不需要暫停的。當需要暫停if_id模塊時,PC寄存器也會同時被暫停。當需要暫停id_ex模塊時,那么整條流水線都會被暫停。

第13行,將PC寄存器的值加4。在這里可以知道,tinyriscv的取指地址是4字節(jié)對齊的,每條指令都是32位的。

通用寄存器

通用寄存器模塊所在的源文件:rtl/core/regs.v

一共有32個通用寄存器x0~x31,其中寄存器x0是只讀寄存器并且其值固定為0。

通用寄存器的輸入輸出信號如下表所示:

58bcf102-5c60-11ed-a3b6-dac502259ad0.png

注意,這里的寄存器1不是指x1寄存器,寄存器2也不是指x2寄存器。而是指一條指令里涉及到的兩個寄存器(源寄存器1和源寄存器2)。一條指令可能會同時讀取兩個寄存器的值,所以有兩個讀端口。又因為jtag模塊也會進行寄存器的讀操作,所以一共有三個讀端口。

讀寄存器操作來自譯碼模塊,并且讀出來的寄存器數(shù)據(jù)也會返回給譯碼模塊。寫寄存器操作來自執(zhí)行模塊。

先看讀操作的代碼,如下:

 1//讀寄存器1
 2always@(*)begin
 3if(rst==`RstEnable)begin
 4rdata1_o=`ZeroWord;
 5endelseif(raddr1_i==`RegNumLog2'h0)begin
 6rdata1_o=`ZeroWord;
 7//如果讀地址等于寫地址,并且正在寫操作,則直接返回寫數(shù)據(jù)
 8endelseif(raddr1_i==waddr_i&&we_i==`WriteEnable)begin
 9rdata1_o=wdata_i;
10endelsebegin
11rdata1_o=regs[raddr1_i];
12end
13end
14
15//讀寄存器2
16always@(*)begin
17if(rst==`RstEnable)begin
18rdata2_o=`ZeroWord;
19endelseif(raddr2_i==`RegNumLog2'h0)begin
20rdata2_o=`ZeroWord;
21//如果讀地址等于寫地址,并且正在寫操作,則直接返回寫數(shù)據(jù)
22endelseif(raddr2_i==waddr_i&&we_i==`WriteEnable)begin
23rdata2_o=wdata_i;
24endelsebegin
25rdata2_o=regs[raddr2_i];
26end
27end
28

可以看到兩個寄存器的讀操作幾乎是一樣的。因此在這里只解析讀寄存器1那部分代碼。

第5行,如果是讀寄存器0(x0),那么直接返回0就可以了。

第8行,這涉及到數(shù)據(jù)相關(guān)問題。由于流水線的原因,當前指令處于執(zhí)行階段的時候,下一條指令則處于譯碼階段。由于執(zhí)行階段不會寫寄存器,而是在下一個時鐘到來時才會進行寄存器寫操作,如果譯碼階段的指令需要上一條指令的結(jié)果,那么此時讀到的寄存器的值是錯誤的。比如下面這兩條指令:

1addx1,x2,x3
2addx4,x1,x5

第二條指令依賴于第一條指令的結(jié)果。為了解決這個數(shù)據(jù)相關(guān)的問題就有了第8~9行的操作,即如果讀寄存器等于寫寄存器,則直接將要寫的值返回給讀操作。

第11行,如果沒有數(shù)據(jù)相關(guān),則返回要讀的寄存器的值。

下面看寫寄存器操作,代碼如下:

 1//寫寄存器
 2always@(posedgeclk)begin
 3if(rst==`RstDisable)begin
 4//優(yōu)先ex模塊寫操作
 5if((we_i==`WriteEnable)&&(waddr_i!=`RegNumLog2'h0))begin
 6regs[waddr_i]<=?wdata_i;
 7????????end?else?if?((jtag_we_i?==?`WriteEnable)?&&?(jtag_addr_i?!=?`RegNumLog2'h0))?begin
 8????????????regs[jtag_addr_i]?<=?jtag_data_i;
 9????????end
10????end
11end

第5~6行,如果執(zhí)行模塊寫使能并且要寫的寄存器不是x0寄存器,則將要寫的值寫到對應(yīng)的寄存器。

第7~8行,jtag模塊的寫操作。

CSR寄存器模塊(csr_reg.v)和通用寄存器模塊的讀、寫操作是類似的,這里就不重復(fù)了。

取指

目前tinyriscv所有外設(shè)(包括rom和ram)、寄存器的讀取都是與時鐘無關(guān)的,或者說所有外設(shè)、寄存器的讀取采用的是組合邏輯的方式。這一點非常重要!

tinyriscv并沒有具體的取指模塊和代碼。PC寄存器模塊的輸出pc_o會連接到外設(shè)rom模塊的地址輸入,又由于rom的讀取是組合邏輯,因此每一個時鐘上升沿到來之前(時序是滿足要求的),從rom輸出的指令已經(jīng)穩(wěn)定在if_id模塊的輸入,當時鐘上升沿到來時指令就會輸出到id模塊。

取到的指令和指令地址會輸入到if_id模塊(if_id.v),if_id模塊是一個時序電路,作用是將輸入的信號打一拍后再輸出到譯碼(id.v)模塊。

譯碼

譯碼模塊所在的源文件:rtl/core/id.v

譯碼(id)模塊是一個純組合邏輯電路,主要作用有以下幾點:

1.根據(jù)指令內(nèi)容,解析出當前具體是哪一條指令(比如add指令)。

2.根據(jù)具體的指令,確定當前指令涉及的寄存器。比如讀寄存器是一個還是兩個,是否需要寫寄存器以及寫哪一個寄存器。

3.訪問通用寄存器,得到要讀的寄存器的值。

譯碼模塊的輸入輸出信號如下表所示:

58e03b58-5c60-11ed-a3b6-dac502259ad0.png

以add指令為例來說明如何譯碼。下圖是add指令的編碼格式:

5929962c-5c60-11ed-a3b6-dac502259ad0.png

可知,add指令被編碼成6部分內(nèi)容。通過第1、4、6這三部分可以唯一確定當前指令是否是add指令。知道是add指令之后,就可以知道add指令需要讀兩個通用寄存器(rs1和rs2)和寫一個通用寄存器(rd)。下面看具體的代碼:

 1case(opcode)
 2...
 3`INST_TYPE_R_M:begin
 4if((funct7==7'b0000000)||(funct7==7'b0100000))begin
 5case(funct3)
 6`INST_ADD_SUB,`INST_SLL,`INST_SLT,`INST_SLTU,`INST_XOR,`INST_SR,`INST_OR,`INST_AND:begin
 7reg_we_o=`WriteEnable;
 8reg_waddr_o=rd;
 9reg1_raddr_o=rs1;
10reg2_raddr_o=rs2;
11end
12...
13

第1行,opcode就是指令編碼中的第6部分內(nèi)容。

第3行,`INST_TYPE_R_M的值為7’b0110011。

第4行,funct7是指指令編碼中的第1部分內(nèi)容。

第5行,funct3是指指令編碼中的第4部分內(nèi)容。

第6行,到了這里,第1、4、6這三部分已經(jīng)譯碼完畢,已經(jīng)可以確定當前指令是add指令了。

第7行,設(shè)置寫寄存器標志為1,表示執(zhí)行模塊結(jié)束后的下一個時鐘需要寫寄存器。

第8行,設(shè)置寫寄存器地址為rd,rd的值為指令編碼里的第5部分內(nèi)容。

第9行,設(shè)置讀寄存器1的地址為rs1,rs1的值為指令編碼里的第3部分內(nèi)容。

第10行,設(shè)置讀寄存器2的地址為rs2,rs2的值為指令編碼里的第2部分內(nèi)容。

其他指令的譯碼過程是類似的,這里就不重復(fù)了。譯碼模塊看起來代碼很多,但是大部分代碼都是類似的。

譯碼模塊還有個作用是當指令為加載內(nèi)存指令(比如lw等)時,向總線發(fā)出請求訪問內(nèi)存的信號。這部分內(nèi)容將在總線一節(jié)再分析。

譯碼模塊的輸出會送到id_ex模塊(id_ex.v)的輸入,id_ex模塊是一個時序電路,作用是將輸入的信號打一拍后再輸出到執(zhí)行模塊(ex.v)。

執(zhí)行

執(zhí)行模塊所在的源文件:rtl/core/ex.v

執(zhí)行(ex)模塊是一個純組合邏輯電路,主要作用有以下幾點:

1.根據(jù)當前是什么指令執(zhí)行對應(yīng)的操作,比如add指令,則將寄存器1的值和寄存器2的值相加。

2.如果是內(nèi)存加載指令,則讀取對應(yīng)地址的內(nèi)存數(shù)據(jù)。

3.如果是跳轉(zhuǎn)指令,則發(fā)出跳轉(zhuǎn)信號。

執(zhí)行模塊的輸入輸出信號如下表所示:

593c9682-5c60-11ed-a3b6-dac502259ad0.png

59677992-5c60-11ed-a3b6-dac502259ad0.png

下面以add指令為例說明,add指令的作用就是將寄存器1的值和寄存器2的值相加,最后將結(jié)果寫入目的寄存器。代碼如下:

 1...
 2`INST_TYPE_R_M:begin
 3if((funct7==7'b0000000)||(funct7==7'b0100000))begin
 4case(funct3)
 5`INST_ADD_SUB:begin
 6jump_flag=`JumpDisable;
 7hold_flag=`HoldDisable;
 8jump_addr=`ZeroWord;
 9mem_wdata_o=`ZeroWord;
10mem_raddr_o=`ZeroWord;
11mem_waddr_o=`ZeroWord;
12mem_we=`WriteDisable;
13if(inst_i[30]==1'b0)begin
14reg_wdata=reg1_rdata_i+reg2_rdata_i;
15endelsebegin
16reg_wdata=reg1_rdata_i-reg2_rdata_i;
17end
18...
19end
20...

第2~4行,譯碼操作。

第5行,對add或sub指令進行處理。

第6~12行,當前指令不涉及到的操作(比如跳轉(zhuǎn)、寫內(nèi)存等)需要將其置回默認值。

第13行,指令編碼中的第30位區(qū)分是add指令還是sub指令。0表示add指令,1表示sub指令。

第14行,執(zhí)行加法操作。

第16行,執(zhí)行減法操作。

其他指令的執(zhí)行是類似的,需要注意的是沒有涉及的信號要將其置為默認值,if和case情況要寫全,避免產(chǎn)生鎖存器。

下面以beq指令說明跳轉(zhuǎn)指令的執(zhí)行。beq指令的編碼如下:

59d79f24-5c60-11ed-a3b6-dac502259ad0.png

beq指令的作用就是當寄存器1的值和寄存器2的值相等時發(fā)生跳轉(zhuǎn),跳轉(zhuǎn)的目的地址為當前指令的地址加上符號擴展的imm的值。具體代碼如下:

 1...
 2`INST_TYPE_B:begin
 3case(funct3)
 4`INST_BEQ:begin
 5hold_flag=`HoldDisable;
 6mem_wdata_o=`ZeroWord;
 7mem_raddr_o=`ZeroWord;
 8mem_waddr_o=`ZeroWord;
 9mem_we=`WriteDisable;
10reg_wdata=`ZeroWord;
11if(reg1_rdata_i==reg2_rdata_i)begin
12jump_flag=`JumpEnable;
13jump_addr=inst_addr_i+{{20{inst_i[31]}},inst_i[7],inst_i[30:25],inst_i[11:8],1'b0};
14endelsebegin
15jump_flag=`JumpDisable;
16jump_addr=`ZeroWord;
17end
18...
19end
20...

第2~4行,譯碼出beq指令。

第5~10行,沒有涉及的信號置為默認值。

第11行,判斷寄存器1的值是否等于寄存器2的值。

第12行,跳轉(zhuǎn)使能,即發(fā)生跳轉(zhuǎn)。

第13行,計算出跳轉(zhuǎn)的目的地址。

第15、16行,不發(fā)生跳轉(zhuǎn)。

其他跳轉(zhuǎn)指令的執(zhí)行是類似的,這里就不再重復(fù)了。

訪存

由于tinyriscv只有三級流水線,因此沒有訪存這個階段,訪存的操作放在了執(zhí)行模塊中。具體是這樣的,在譯碼階段如果識別出是內(nèi)存訪問指令(lb、lh、lw、lbu、lhu、sb、sh、sw),則向總線發(fā)出內(nèi)存訪問請求,具體代碼(位于id.v)如下:

 1...
 2`INST_TYPE_L:begin
 3case(funct3)
 4`INST_LB,`INST_LH,`INST_LW,`INST_LBU,`INST_LHU:begin
 5reg1_raddr_o=rs1;
 6reg2_raddr_o=`ZeroReg;
 7reg_we_o=`WriteEnable;
 8reg_waddr_o=rd;
 9mem_req=`RIB_REQ;
10end
11default:begin
12reg1_raddr_o=`ZeroReg;
13reg2_raddr_o=`ZeroReg;
14reg_we_o=`WriteDisable;
15reg_waddr_o=`ZeroReg;
16end
17endcase
18end
19`INST_TYPE_S:begin
20case(funct3)
21`INST_SB,`INST_SW,`INST_SH:begin
22reg1_raddr_o=rs1;
23reg2_raddr_o=rs2;
24reg_we_o=`WriteDisable;
25reg_waddr_o=`ZeroReg;
26mem_req=`RIB_REQ;
27end
28...

第2~4行,譯碼出內(nèi)存加載指令,lb、lh、lw、lbu、lhu。

第5行,需要讀寄存器1。

第6行,不需要讀寄存器2。

第7行,寫目的寄存器使能。

第8行,寫目的寄存器的地址,即寫哪一個通用寄存器。

第9行,發(fā)出訪問內(nèi)存請求。

第19~21行,譯碼出內(nèi)存存儲指令,sb、sw、sh。

第22行,需要讀寄存器1。

第23行,需要讀寄存器2。

第24行,不需要寫目的寄存器。

第26行,發(fā)出訪問內(nèi)存請求。

問題來了,為什么在取指階段發(fā)出內(nèi)存訪問請求?這跟總線的設(shè)計是相關(guān)的,這里先不具體介紹總線的設(shè)計,只需要知道如果需要訪問內(nèi)存,則需要提前一個時鐘向總線發(fā)出請求。

在譯碼階段向總線發(fā)出內(nèi)存訪問請求后,在執(zhí)行階段就會得到對應(yīng)的內(nèi)存數(shù)據(jù)。

下面看執(zhí)行階段的內(nèi)存加載操作,以lb指令為例,lb指令的作用是訪問內(nèi)存中的某一個字節(jié),代碼(位于ex.v)如下:

 1...
 2`INST_TYPE_L:begin
 3case(funct3)
 4`INST_LB:begin
 5jump_flag=`JumpDisable;
 6hold_flag=`HoldDisable;
 7jump_addr=`ZeroWord;
 8mem_wdata_o=`ZeroWord;
 9mem_waddr_o=`ZeroWord;
10mem_we=`WriteDisable;
11mem_raddr_o=reg1_rdata_i+{{20{inst_i[31]}},inst_i[31:20]};
12case(mem_raddr_index)
132'b00:begin
14reg_wdata={{24{mem_rdata_i[7]}},mem_rdata_i[7:0]};
15end
162'b01:begin
17reg_wdata={{24{mem_rdata_i[15]}},mem_rdata_i[15:8]};
18end
192'b10:begin
20reg_wdata={{24{mem_rdata_i[23]}},mem_rdata_i[23:16]};
21end
22default:begin
23reg_wdata={{24{mem_rdata_i[31]}},mem_rdata_i[31:24]};
24end
25endcase
26end
27...
28

第2~4行,譯碼出lb指令。

第5~10行,將沒有涉及的信號置為默認值。

第11行,得到訪存的地址。

第12行,由于訪問內(nèi)存的地址必須是4字節(jié)對齊的,因此這里的mem_raddr_index的含義就是32位內(nèi)存數(shù)據(jù)(4個字節(jié))中的哪一個字節(jié),2’b00表示第0個字節(jié),即最低字節(jié),2’b01表示第1個字節(jié),2’b10表示第2個字節(jié),2’b11表示第3個字節(jié),即最高字節(jié)。

第14、17、20、23行,寫寄存器數(shù)據(jù)。

回寫

由于tinyriscv只有三級流水線,因此也沒有回寫(write back,或者說寫回)這個階段,在執(zhí)行階段結(jié)束后的下一個時鐘上升沿就會把數(shù)據(jù)寫回寄存器或者內(nèi)存。

需要注意的是,在執(zhí)行階段,判斷如果是內(nèi)存存儲指令(sb、sh、sw),則向總線發(fā)出訪問內(nèi)存請求。而對于內(nèi)存加載(lb、lh、lw、lbu、lhu)指令是不需要的。因為內(nèi)存存儲指令既需要加載內(nèi)存數(shù)據(jù)又需要往內(nèi)存存儲數(shù)據(jù)。

以sb指令為例,代碼(位于ex.v)如下:

 1...
 2`INST_TYPE_S:begin
 3case(funct3)
 4`INST_SB:begin
 5jump_flag=`JumpDisable;
 6hold_flag=`HoldDisable;
 7jump_addr=`ZeroWord;
 8reg_wdata=`ZeroWord;
 9mem_we=`WriteEnable;
10mem_req=`RIB_REQ;
11mem_waddr_o=reg1_rdata_i+{{20{inst_i[31]}},inst_i[31:25],inst_i[11:7]};
12mem_raddr_o=reg1_rdata_i+{{20{inst_i[31]}},inst_i[31:25],inst_i[11:7]};
13case(mem_waddr_index)
142'b00:begin
15mem_wdata_o={mem_rdata_i[31:8],reg2_rdata_i[7:0]};
16end
172'b01:begin
18mem_wdata_o={mem_rdata_i[31:16],reg2_rdata_i[7:0],mem_rdata_i[7:0]};
19end
202'b10:begin
21mem_wdata_o={mem_rdata_i[31:24],reg2_rdata_i[7:0],mem_rdata_i[15:0]};
22end
23default:begin
24mem_wdata_o={reg2_rdata_i[7:0],mem_rdata_i[23:0]};
25end
26endcase
27end
28...
29

第2~4行,譯碼出sb指令。

第5~8行,將沒有涉及的信號置為默認值。

第9行,寫內(nèi)存使能。

第10行,發(fā)出訪問內(nèi)存請求。

第11行,內(nèi)存寫地址。

第12行,內(nèi)存讀地址,讀地址和寫地址是一樣的。

第13行,mem_waddr_index的含義就是寫32位內(nèi)存數(shù)據(jù)中的哪一個字節(jié)。

第15、18、21、24行,寫內(nèi)存數(shù)據(jù)。

sb指令只改變讀出來的32位內(nèi)存數(shù)據(jù)中對應(yīng)的字節(jié),其他3個字節(jié)的數(shù)據(jù)保持不變,然后寫回到內(nèi)存中。

跳轉(zhuǎn)和流水線暫停

跳轉(zhuǎn)就是改變PC寄存器的值。又因為跳轉(zhuǎn)與否需要在執(zhí)行階段才知道,所以當需要跳轉(zhuǎn)時,則需要暫停流水線(正確來說是沖刷流水線。流水線是不可以暫停的,除非時鐘不跑了)。那怎么暫停流水線呢?或者說怎么實現(xiàn)流水線沖刷呢?tinyriscv的流水線結(jié)構(gòu)如下圖所示。

59f3e0b2-5c60-11ed-a3b6-dac502259ad0.png


其中長方形表示的是時序邏輯電路,云狀型表示的是組合邏輯電路。在執(zhí)行階段,當判斷需要發(fā)生跳轉(zhuǎn)時,發(fā)出跳轉(zhuǎn)信號和跳轉(zhuǎn)地址給ctrl(ctrl.v)模塊。ctrl模塊判斷跳轉(zhuǎn)信號有效后會給pc_reg、if_id和id_ex模塊發(fā)出流水線暫停信號,并且還會給pc_reg模塊發(fā)出跳轉(zhuǎn)地址。在時鐘上升沿到來時,if_id和id_ex模塊如果檢測到流水線暫停信號有效則送出NOP指令,從而使得整條流水線(譯碼階段、執(zhí)行階段)流淌的都是NOP指令,已經(jīng)取出的指令就會無效,這就是流水線沖刷機制。

下面看ctrl.v模塊是怎么設(shè)計的。ctrl.v的輸入輸出信號如下表所示:

5a17d9e0-5c60-11ed-a3b6-dac502259ad0.png

可知,暫停信號來自多個模塊。對于跳轉(zhuǎn)(跳轉(zhuǎn)包含暫停流水線操作),是要沖刷整條流水線的,因為跳轉(zhuǎn)后流水線上其他階段的其他操作是無效的。對于其他模塊的暫停信號,一種最簡單的設(shè)計就是也沖刷整條流水線,但是這樣的話MCU的效率就會低一些。另一種設(shè)計就是根據(jù)不同的暫停信號,暫停不同的流水線階段。比如對于總線請求的暫停只需要暫停PC寄存器這一階段就可以了,讓流水線上的其他階段繼續(xù)工作。看ctrl.v的代碼:

 1...
 2always@(*)begin
 3if(rst==`RstEnable)begin
 4hold_flag_o=`Hold_None;
 5jump_flag_o=`JumpDisable;
 6jump_addr_o=`ZeroWord;
 7endelsebegin
 8jump_addr_o=jump_addr_i;
 9jump_flag_o=jump_flag_i;
10//默認不暫停
11hold_flag_o=`Hold_None;
12//按優(yōu)先級處理不同模塊的請求
13if(jump_flag_i==`JumpEnable||hold_flag_ex_i==`HoldEnable||hold_flag_clint_i==`HoldEnable)begin
14//暫停整條流水線
15hold_flag_o=`Hold_Id;
16endelseif(hold_flag_rib_i==`HoldEnable)begin
17//暫停PC,即取指地址不變
18hold_flag_o=`Hold_Pc;
19endelseif(jtag_halt_flag_i==`HoldEnable)begin
20//暫停整條流水線
21hold_flag_o=`Hold_Id;
22endelsebegin
23hold_flag_o=`Hold_None;
24end
25end
26end
27...

第3~6行,復(fù)位時賦默認值。

第8行,輸出跳轉(zhuǎn)地址直接等于輸入跳轉(zhuǎn)地址。

第9行,輸出跳轉(zhuǎn)標志直接等于輸入跳轉(zhuǎn)標志。

第11行,默認不暫停流水線。

第13、14行,對于跳轉(zhuǎn)操作、來自執(zhí)行階段的暫停、來自中斷模塊的暫停則暫停整條流水線。

第16~18行,對于總線暫停,只需要暫停PC寄存器,讓譯碼和執(zhí)行階段繼續(xù)運行。

第19~21行,對于jtag模塊暫停,則暫停整條流水線。

跳轉(zhuǎn)時只需要暫停流水線一個時鐘周期,但是如果是多周期指令(比如除法指令),則需要暫停流水線多個時鐘周期。

總線

設(shè)想一下一個沒有總線的SOC,處理器核與外設(shè)之間的連接是怎樣的。可能會如下圖所示:

5a2f47ba-5c60-11ed-a3b6-dac502259ad0.png

可見,處理器核core直接與每個外設(shè)進行交互。假設(shè)一個外設(shè)有一條地址總線和一條數(shù)據(jù)總線,總共有N個外設(shè),那么處理器核就有N條地址總線和N條數(shù)據(jù)總線,而且每增加一個外設(shè)就要修改(改動還不小)core的代碼。有了總線之后(見本章開頭的圖2_1),處理器核只需要一條地址總線和一條數(shù)據(jù)總線,大大簡化了處理器核與外設(shè)之間的連接。

目前已經(jīng)有不少成熟、標準的總線,比如AMBA、wishbone、AXI等。設(shè)計CPU時大可以直接使用其中某一種,以節(jié)省開發(fā)時間。但是為了追求簡單,tinyriscv并沒有使用這些總線,而是自主設(shè)計了一種名為RIB(RISC-V Internal Bus)的總線。RIB總線支持多主多從連接,但是同一時刻只支持一主一從通信。RIB總線上的各個主設(shè)備之間采用固定優(yōu)先級仲裁機制。

RIB總線模塊所在的源文件:rtl/core/rib.v

RIB總線模塊的輸入輸出信號如下表所示(由于各個主、從之間的信號是類似的,所以這里只列出其中一個主和一個從的信號):

5a47f0d0-5c60-11ed-a3b6-dac502259ad0.png

RIB總線本質(zhì)上是一個多路選擇器,從多個主設(shè)備中選擇其中一個來訪問對應(yīng)的從設(shè)備。

RIB總線地址的最高4位決定要訪問的是哪一個從設(shè)備,因此最多支持16個從設(shè)備。

仲裁方式采用的類似狀態(tài)機的方式來實現(xiàn),代碼如下所示:

 1...
 2//主設(shè)備請求信號
 3assignreq={m2_req_i,m1_req_i,m0_req_i};
 4
 5
 6//授權(quán)主設(shè)備切換
 7always@(posedgeclk)begin
 8if(rst==`RstEnable)begin
 9grant<=?grant1;
10????????end?else?begin
11????????????grant?<=?next_grant;
12????????end
13????end
14
15????//?仲裁邏輯
16????//?固定優(yōu)先級仲裁機制
17????//?優(yōu)先級由高到低:主設(shè)備0,主設(shè)備2,主設(shè)備1
18????always?@?(*)?begin
19????????if?(rst?==?`RstEnable)?begin
20????????????next_grant?=?grant1;
21????????????hold_flag_o?=?`HoldDisable;
22????????end?else?begin
23????????????case?(grant)
24????????????????grant0:?begin
25????????????????????if?(req[0])?begin
26????????????????????????next_grant?=?grant0;
27????????????????????????hold_flag_o?=?`HoldEnable;
28????????????????????end?else?if?(req[2])?begin
29????????????????????????next_grant?=?grant2;
30????????????????????????hold_flag_o?=?`HoldEnable;
31????????????????????end?else?begin
32????????????????????????next_grant?=?grant1;
33????????????????????????hold_flag_o?=?`HoldDisable;
34????????????????????end
35????????????????end
36????????????????grant1:?begin
37????????????????????if?(req[0])?begin
38????????????????????????next_grant?=?grant0;
39????????????????????????hold_flag_o?=?`HoldEnable;
40????????????????????end?else?if?(req[2])?begin
41????????????????????????next_grant?=?grant2;
42????????????????????????hold_flag_o?=?`HoldEnable;
43????????????????????end?else?begin
44????????????????????????next_grant?=?grant1;
45????????????????????????hold_flag_o?=?`HoldDisable;
46????????????????????end
47????????????????end
48????????????????grant2:?begin
49????????????????????if?(req[0])?begin
50????????????????????????next_grant?=?grant0;
51????????????????????????hold_flag_o?=?`HoldEnable;
52????????????????????end?else?if?(req[2])?begin
53????????????????????????next_grant?=?grant2;
54????????????????????????hold_flag_o?=?`HoldEnable;
55????????????????????end?else?begin
56????????????????????????next_grant?=?grant1;
57????????????????????????hold_flag_o?=?`HoldDisable;
58????????????????????end
59????????????????end
60????????????????default:?begin
61????????????????????next_grant?=?grant1;
62????????????????????hold_flag_o?=?`HoldDisable;
63????????????????end
64????????????endcase
65????????end
66????end
67...

第3行,主設(shè)備請求信號的組合。

第7~13行,切換主設(shè)備操作,默認是授權(quán)給主設(shè)備1的,即取指模塊。從這里可以知道,從發(fā)出總線訪問請求后,需要一個時鐘周期才能完成切換。

第18~66行,通過組合邏輯電路來實現(xiàn)優(yōu)先級仲裁。

第20行,默認授權(quán)給主設(shè)備1。

第24~35行,這是已經(jīng)授權(quán)給主設(shè)備0的情況。第25、28、31行,分別對應(yīng)主設(shè)備0、主設(shè)備2和主設(shè)備1的請求,通過if、else語句來實現(xiàn)優(yōu)先級。第27、30行,主設(shè)備0和主設(shè)備2的請求需要暫停流水線,這里只需要暫停PC階段,讓譯碼和執(zhí)行階段繼續(xù)執(zhí)行。

第3647行,這是已經(jīng)授權(quán)給主設(shè)備1的情況,和第2435行的操作是類似的。

第4859行,這是已經(jīng)授權(quán)給主設(shè)備2的情況,和第2435行的操作是類似的。

注意:RIB總線上不同的主設(shè)備切換是需要一個時鐘周期的,因此如果想要在執(zhí)行階段讀取到外設(shè)的數(shù)據(jù),則需要在譯碼階段就發(fā)出總線訪問請求。

中斷

中斷(中斷返回)本質(zhì)上也是一種跳轉(zhuǎn),只不過還需要附加一些讀寫CSR寄存器的操作。

RISC-V中斷分為兩種類型,一種是同步中斷,即ECALL、EBREAK等指令所產(chǎn)生的中斷,另一種是異步中斷,即GPIO、UART等外設(shè)產(chǎn)生的中斷。

對于中斷模塊設(shè)計,一種簡單的方法就是當檢測到中斷(中斷返回)信號時,先暫停整條流水線,設(shè)置跳轉(zhuǎn)地址為中斷入口地址,然后讀、寫必要的CSR寄存器(mstatus、mepc、mcause等),等讀寫完這些CSR寄存器后取消流水線暫停,這樣處理器就可以從中斷入口地址開始取指,進入中斷服務(wù)程序。

下面看tinyriscv的中斷是如何設(shè)計的。中斷模塊所在文件:rtl/core/clint.v

輸入輸出信號列表如下:

5a6711ae-5c60-11ed-a3b6-dac502259ad0.png

先看中斷模塊是怎樣判斷有中斷信號產(chǎn)生的,如下代碼:

 1...
 2always@(*)begin
 3if(rst==`RstEnable)begin
 4int_state=S_INT_IDLE;
 5endelsebegin
 6if(inst_i==`INST_ECALL||inst_i==`INST_EBREAK)begin
 7int_state=S_INT_SYNC_ASSERT;
 8endelseif(int_flag_i!=`INT_NONE&&global_int_en_i==`True)begin
 9int_state=S_INT_ASYNC_ASSERT;
10endelseif(inst_i==`INST_MRET)begin
11int_state=S_INT_MRET;
12endelsebegin
13int_state=S_INT_IDLE;
14end
15end
16end
17...

第3~4行,復(fù)位后的狀態(tài),默認沒有中斷要處理。

第6~7行,判斷當前指令是否是ECALL或者EBREAK指令,如果是則設(shè)置中斷狀態(tài)為S_INT_SYNC_ASSERT,表示有同步中斷要處理。

第8~9行,判斷是否有外設(shè)中斷信號產(chǎn)生,如果是則設(shè)置中斷狀態(tài)為S_INT_ASYNC_ASSERT,表示有異步中斷要處理。

第10~11行,判斷當前指令是否是MRET指令,MRET指令是中斷返回指令。如果是,則設(shè)置中斷狀態(tài)為S_INT_MRET。

下面就根據(jù)當前的中斷狀態(tài)做不同處理(讀寫不同的CSR寄存器),代碼如下:

 1...
 2always@(posedgeclk)begin
 3if(rst==`RstEnable)begin
 4csr_state<=?S_CSR_IDLE;
 5????????????cause?<=?`ZeroWord;
 6????????????inst_addr?<=?`ZeroWord;
 7????????end?else?begin
 8????????????case?(csr_state)
 9????????????????S_CSR_IDLE:?begin
10????????????????????if?(int_state?==?S_INT_SYNC_ASSERT)?begin
11????????????????????????csr_state?<=?S_CSR_MEPC;
12????????????????????????inst_addr?<=?inst_addr_i;
13????????????????????????case?(inst_i)
14????????????????????????????`INST_ECALL:?begin
15????????????????????????????????cause?<=?32'd11;
16????????????????????????????end
17????????????????????????????`INST_EBREAK:?begin
18????????????????????????????????cause?<=?32'd3;
19????????????????????????????end
20????????????????????????????default:?begin
21????????????????????????????????cause?<=?32'd10;
22????????????????????????????end
23????????????????????????endcase
24????????????????????end?else?if?(int_state?==?S_INT_ASYNC_ASSERT)?begin
25????????????????????????//?定時器中斷
26????????????????????????cause?<=?32'h80000004;
27????????????????????????csr_state?<=?S_CSR_MEPC;
28????????????????????????inst_addr?<=?inst_addr_i;
29????????????????????//?中斷返回
30????????????????????end?else?if?(int_state?==?S_INT_MRET)?begin
31????????????????????????csr_state?<=?S_CSR_MSTATUS_MRET;
32????????????????????end
33????????????????end
34????????????????S_CSR_MEPC:?begin
35????????????????????csr_state?<=?S_CSR_MCAUSE;
36????????????????end
37????????????????S_CSR_MCAUSE:?begin
38????????????????????csr_state?<=?S_CSR_MSTATUS;
39????????????????end
40????????????????S_CSR_MSTATUS:?begin
41????????????????????csr_state?<=?S_CSR_IDLE;
42????????????????end
43????????????????S_CSR_MSTATUS_MRET:?begin
44????????????????????csr_state?<=?S_CSR_IDLE;
45????????????????end
46????????????????default:?begin
47????????????????????csr_state?<=?S_CSR_IDLE;
48????????????????end
49????????????endcase
50????????end
51????end
52...

第3~6行,CSR狀態(tài)默認處于S_CSR_IDLE。

第1023行,當CSR處于S_CSR_IDLE時,如果中斷狀態(tài)為S_INT_SYNC_ASSERT,則在第11行將CSR狀態(tài)設(shè)置為S_CSR_MEPC,在第12行將當前指令地址保存下來。在第1323行,根據(jù)不同的指令類型,設(shè)置不同的中斷碼(Exception Code),這樣在中斷服務(wù)程序里就可以知道當前中斷發(fā)生的原因了。

第24~28行,目前tinyriscv只支持定時器這個外設(shè)中斷。

第30~31行,如果是中斷返回指令,則設(shè)置CSR狀態(tài)為S_CSR_MSTATUS_MRET。

第34~48行,一個時鐘切換一下CSR狀態(tài)。

接下來就是寫CSR寄存器操作,需要根據(jù)上面的CSR狀態(tài)來寫。

 1...
 2//發(fā)出中斷信號前,先寫幾個CSR寄存器
 3always@(posedgeclk)begin
 4if(rst==`RstEnable)begin
 5we_o<=?`WriteDisable;
 6????????????waddr_o?<=?`ZeroWord;
 7????????????data_o?<=?`ZeroWord;
 8????????end?else?begin
 9????????????case?(csr_state)
10????????????????//?將mepc寄存器的值設(shè)為當前指令地址
11????????????????S_CSR_MEPC:?begin
12????????????????????we_o?<=?`WriteEnable;
13????????????????????waddr_o?<=?{20'h0,?`CSR_MEPC};
14????????????????????data_o?<=?inst_addr;
15????????????????end
16????????????????//?寫中斷產(chǎn)生的原因
17????????????????S_CSR_MCAUSE:?begin
18????????????????????we_o?<=?`WriteEnable;
19????????????????????waddr_o?<=?{20'h0,?`CSR_MCAUSE};
20????????????????????data_o?<=?cause;
21????????????????end
22????????????????//?關(guān)閉全局中斷
23????????????????S_CSR_MSTATUS:?begin
24????????????????????we_o?<=?`WriteEnable;
25????????????????????waddr_o?<=?{20'h0,?`CSR_MSTATUS};
26????????????????????data_o?<=?{csr_mstatus[31:4],?1'b0,?csr_mstatus[2:0]};
27????????????????end
28????????????????//?中斷返回
29????????????????S_CSR_MSTATUS_MRET:?begin
30????????????????????we_o?<=?`WriteEnable;
31????????????????????waddr_o?<=?{20'h0,?`CSR_MSTATUS};
32????????????????????data_o?<=?{csr_mstatus[31:4],?csr_mstatus[7],?csr_mstatus[2:0]};
33????????????????end
34????????????????default:?begin
35????????????????????we_o?<=?`WriteDisable;
36????????????????????waddr_o?<=?`ZeroWord;
37????????????????????data_o?<=?`ZeroWord;
38????????????????end
39????????????endcase
40????????end
41????end
42...

第11~15行,寫mepc寄存器。

第17~21行,寫mcause寄存器。

第23~27行,關(guān)閉全局異步中斷。

第29~33行,寫mstatus寄存器。

最后就是發(fā)出中斷信號,中斷信號會進入到執(zhí)行階段。

 1...
 2//發(fā)出中斷信號給ex模塊
 3always@(posedgeclk)begin
 4if(rst==`RstEnable)begin
 5int_assert_o<=?`INT_DEASSERT;
 6????????????int_addr_o?<=?`ZeroWord;
 7????????end?else?begin
 8????????????//?發(fā)出中斷進入信號.寫完mstatus寄存器才能發(fā)
 9????????????if?(csr_state?==?S_CSR_MSTATUS)?begin
10????????????????int_assert_o?<=?`INT_ASSERT;
11????????????????int_addr_o?<=?csr_mtvec;
12????????????//?發(fā)出中斷返回信號
13????????????end?else?if?(csr_state?==?S_CSR_MSTATUS_MRET)?begin
14????????????????int_assert_o?<=?`INT_ASSERT;
15????????????????int_addr_o?<=?csr_mepc;
16????????????end?else?begin
17????????????????int_assert_o?<=?`INT_DEASSERT;
18????????????????int_addr_o?<=?`ZeroWord;
19????????????end
20????????end
21????end
22...

有兩種情況需要發(fā)出中斷信號,一種是進入中斷,另一種是退出中斷。

第9~12行,寫完mstatus寄存器后發(fā)出中斷進入信號,中斷入口地址就是mtvec寄存器的值。

第13~15行,發(fā)出中斷退出信號,中斷退出地址就是mepc寄存器的值。

JTAG

JTAG作為一種調(diào)試接口,在處理器設(shè)計里算是比較大而且復(fù)雜、卻不起眼的一個模塊,絕大部分開源處理器核都沒有JTAG(調(diào)試)模塊。但是為了完整性,tinyriscv還是加入了JTAG模塊,還單獨為JTAG寫了一篇文章《深入淺出RISC-V調(diào)試》,感興趣的同學可以去看一下,這里不再單獨介紹了。要明白JTAG模塊的設(shè)計原理,必須先看懂RISC-V的debug spec。

RTL仿真驗證

寫完處理器代碼后,怎么證明所寫的處理器是能正確執(zhí)行指令的呢?這時就需要寫testbench來測試了。其實在寫代碼的時候就應(yīng)該在頭腦里進行仿真。這里并沒有使用ModelSim這些軟件進行仿真,而是使用了一個輕量級的iverilog和vvp工具。

在寫testbench文件時,有兩點需要注意的,第一點就是在testbench文件里加上讀指令文件的操作:

1initialbegin
2$readmemh("inst.data",tinyriscv_soc_top_0.u_rom._rom);
3end

第2行代碼的作用就是將inst.data文件讀入到rom模塊里,inst.data里面的內(nèi)容就是一條條指令,這樣處理器開始執(zhí)行時就可以從rom里取到指令。

第二點就是,在仿真期間將仿真波形dump出到某一個文件里:

1initialbegin
2$dumpfile("tinyriscv_soc_tb.vcd");
3$dumpvars(0,tinyriscv_soc_tb);
4end

這樣仿真波形就會被dump出到tinyriscv_soc_tb.vcd文件,使用gtkwave工具就可以查看波形了。

審核編輯:郭婷

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 數(shù)字電路
    +關(guān)注

    關(guān)注

    193

    文章

    1636

    瀏覽量

    81423
  • 觸發(fā)器
    +關(guān)注

    關(guān)注

    14

    文章

    2029

    瀏覽量

    61754
  • RISC-V
    +關(guān)注

    關(guān)注

    46

    文章

    2461

    瀏覽量

    47988

原文標題:既生ARM,何生RISC-V

文章出處:【微信號:嵌入式情報局,微信公眾號:嵌入式情報局】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    FPGA與RISC-V淺談

    。 Semico Research預(yù)測2025年 RISC-V 芯片市場規(guī)模將突破 450 億美元,年復(fù)合增長率達 58%,國家戰(zhàn)略采購占比超 35%。RISC-V International在報告預(yù)測,搭載
    發(fā)表于 04-11 13:53 ?178次閱讀
    FPGA與<b class='flag-5'>RISC-V</b>淺談

    數(shù)字電路設(shè)計:前端與后端的差異解析

    本文介紹了數(shù)字電路設(shè)計“前端”和“后端”的區(qū)別。 數(shù)字電路設(shè)計“前端”和“后端”整個過程可類比蓋一棟大樓:前端好比建筑師在圖紙上進行功能和布局的抽象設(shè)計,后端則是工程隊把圖紙變成實
    的頭像 發(fā)表于 02-12 10:09 ?369次閱讀

    如何使用 Verilog 進行數(shù)字電路設(shè)計

    使用Verilog進行數(shù)字電路設(shè)計是一個復(fù)雜但有序的過程,它涉及從概念設(shè)計到實現(xiàn)、驗證和優(yōu)化的多個階段。以下是一個基本的步驟指南,幫助你理解如何使用Verilog來設(shè)計數(shù)字電路: 1. 明確設(shè)計需求
    的頭像 發(fā)表于 12-17 09:47 ?955次閱讀

    RISC-V,即將進入應(yīng)用的爆發(fā)期

    計算機由控制整體的CPU(中央處理器)和加速器兩部分構(gòu)成。在AI計算,功耗和效率是兩個關(guān)鍵因素。RISC-V架構(gòu)通過其簡潔的設(shè)計和定制化的擴展,可以實現(xiàn)高效的能量使用。該架構(gòu)能夠通過小型且高效的處理單元
    發(fā)表于 10-31 16:06

    加入全球 RISC-V Advocate 行列,共筑 RISC-V 的未來 !

    加入RISC-VAdvocate行列!我們正在尋找來自世界各地的RISC-V愛好者,通過全球推廣和參與,成為支持RISC-V進步的關(guān)鍵參與者。作為一名RISC-VAdvocate,您將
    的頭像 發(fā)表于 09-10 08:08 ?713次閱讀
    加入全球 <b class='flag-5'>RISC-V</b> Advocate 行列,共筑 <b class='flag-5'>RISC-V</b> 的未來 !

    RISC-V Summit China 2024 青稞RISC-V+接口PHY,賦能RISC-V高效落地

    沁恒在歷屆峰會上分享RISC-V在MCU領(lǐng)域的創(chuàng)新成果,和大家共同見證了本土RISC-V產(chǎn)業(yè)的成長。早在第一屆RISC-V中國峰會上,沁恒就公開了青稞RISC-V系列量產(chǎn)芯片的關(guān)鍵技術(shù)
    的頭像 發(fā)表于 08-30 18:18 ?1996次閱讀
    <b class='flag-5'>RISC-V</b> Summit China 2024  青稞<b class='flag-5'>RISC-V</b>+接口PHY,賦能<b class='flag-5'>RISC-V</b>高效落地

    RISC-V Summit China 2024 | 青稞RISC-V+接口PHY,賦能RISC-V高效落地

    方式,從同質(zhì)化的市場環(huán)境脫穎而出,通過專業(yè)接口技術(shù)將RISC-V導(dǎo)入下游廣闊的互聯(lián)互通場景,為萬物互聯(lián)的世界賦能。 03 青稞內(nèi)核+接口PHY,讓RISC-V高效落地 現(xiàn)場,沁恒作了“青稞R
    發(fā)表于 08-30 17:37

    2024 RISC-V 中國峰會:華秋電子助力RISC-V生態(tài)!

    第四屆RISC-V中國峰會(RISC-V Summit China 2024)于8月21日至23日在杭州盛大召開,成為RISC-V領(lǐng)域的一次重要盛會
    的頭像 發(fā)表于 08-26 18:33 ?1263次閱讀
    2024 <b class='flag-5'>RISC-V</b> 中國峰會:華秋電子助力<b class='flag-5'>RISC-V</b>生態(tài)!

    2024 RISC-V 中國峰會:華秋電子助力RISC-V生態(tài)!

    第四屆RISC-V中國峰會(RISC-V Summit China 2024)于8月21日至23日在杭州盛大召開,成為RISC-V領(lǐng)域的一次重要盛會。峰會匯聚了RISC-V國際基金會的
    發(fā)表于 08-26 16:46

    risc-v的發(fā)展歷史

    RISC-V架構(gòu)在學術(shù)圈和開源社區(qū)獲得了更廣泛的關(guān)注和應(yīng)用。 四、廣泛應(yīng)用與生態(tài)系統(tǒng)建設(shè) 工業(yè)界應(yīng)用:隨著RISC-V架構(gòu)的不斷發(fā)展,越來越多的公司開始采用RISC-V架構(gòu)。例如,
    發(fā)表于 07-29 17:20

    rIsc-v的缺的是什么?

    態(tài)系統(tǒng)還不夠豐富。這可能導(dǎo)致軟件和工具的可用性受限,特別是在一些特定的應(yīng)用領(lǐng)域或開發(fā)環(huán)境。開發(fā)者可能需要投入更多的時間和精力來尋找或開發(fā)適合RISC-V架構(gòu)的軟件和工具鏈。 碎片化風險:由于RISC-V的開源性
    發(fā)表于 07-29 17:18

    為什么要有RISC-V

    ,RV32IMFD將乘法(RV32M),單精度浮點RV32F)和雙精度浮點RV32D)的擴展添加到了基礎(chǔ)指令集RV32I)。 繼續(xù)用我們剛才的類比來說,RISC-V提供的是菜單,而不是一頓應(yīng)有盡有的自助餐
    發(fā)表于 07-27 15:05

    淺析RISC-V領(lǐng)先ARM的優(yōu)勢

    、教育以及初創(chuàng)企業(yè)的使用。 定制化能力強: RISC-V采用了模塊化設(shè)計的思路,不同的組件可以靈活進行裁剪與增加。這使得RISC-V架構(gòu)可以根據(jù)具體的應(yīng)用需求進行靈活定制,優(yōu)化并滿足不同的性能、功耗
    發(fā)表于 06-27 08:45

    數(shù)字EDA賦能RISC-V落地演進技術(shù)研討會成功舉辦

    ,主題為《數(shù)字異構(gòu)驗證方案應(yīng)對數(shù)字電路設(shè)計的新挑戰(zhàn)》。梁琪介紹了當前數(shù)字芯片設(shè)計的挑戰(zhàn),特別是RISC-V架構(gòu)的碎片化特征。她指出,這種特
    的頭像 發(fā)表于 06-21 08:24 ?552次閱讀
    <b class='flag-5'>數(shù)字</b>EDA賦能<b class='flag-5'>RISC-V</b>落地演進技術(shù)研討會成功舉辦

    數(shù)字EDA賦能RISC-V落地演進技術(shù)研討會成功舉辦

    為了推動RISC-V技術(shù)的落地與演進, 國家集成電路設(shè)計深圳產(chǎn)業(yè)化基地攜手思爾芯 ,于2024年6月18日下午成功舉辦了“數(shù)字EDA賦能RISC-V落地演進技術(shù)研討會”。
    的頭像 發(fā)表于 06-20 11:15 ?898次閱讀
    主站蜘蛛池模板: 天天色天天综合网 | 精品久久久久久久久久 | 国产精品永久免费 | jizz 大全欧美 | 天天干天天干天天天天天天爽 | 老司机亚洲精品影院在线观看 | 91精选视频在线观看 | 久久天天躁狠狠躁夜夜免费观看 | 精品乱人伦一区二区三区 | xxxx性xx另类| 国产精品va一区二区三区 | 激情有码 | 色成人亚洲 | 日韩高清特级特黄毛片 | 亚洲色图综合图区 | 超级黄色毛片 | 久久久久国产精品免费网站 | 一级片影院 | 亚洲福利秒拍一区二区 | 日韩精品免费一区二区三区 | 国产色网 | 天天操天天碰 | 性久久久久久久久久 | 天天狠天天干 | 亚洲一级免费视频 | 亚洲一级影院 | 思思99re66在线精品免费观看 | 色婷婷影院在线视频免费播放 | 欧美激情 在线 | 无夜精品久久久久久 | 久综合| 伊人黄色网 | 亚1州区2区三区4区产品 | 亚洲a免费| 亚洲第一黄色网址 | 五月天色婷婷丁香 | 在线免费公开视频 | 好爽好紧好大的免费视频国产 | 四虎影院永久网站 | 最好看最新的中文字幕1 | 国产女人水多白浆 |