01、引言
由于接口控制信號上的差異,要實現(xiàn)Bluespec SystemVerilog(BSV)生成的代碼和外部Verilog代碼之間的正確交互是一件比較麻煩同時容易出錯的事情。在BSV中, 模塊之間的交互都是基于Action或ActionValue這兩類method完成。下圖展示了使用BSV設(shè)計的某一模塊的接口定義及其實際生成的硬件端口。由圖可見,除了被顯式地定義為函數(shù)輸入參數(shù)或返回類型的數(shù)據(jù)信號外,每個method都隱含著一對控制信號,即en和rdy。
其中,rdy信號指示該方法已經(jīng)準備好被調(diào)用,而當外部模塊調(diào)用該方法時會拉高對應(yīng)的en信號。en-rdy控制信號和AXI總線中valid-ready信號的作用類似,這兩對信號都保證了當通信雙方都準備好時才能完成一拍數(shù)據(jù)的傳輸。雖然完成的功能相同,但這兩對信號在具體的實現(xiàn)機制上仍存在一定差異:
首先,en-rdy和valid-ready并不是一一對應(yīng)的關(guān)系。對于某個模塊,en永遠是輸入信號,而rdy永遠是輸出信號。而在valid-ready握手協(xié)議中,master端輸出valid接收ready,而slave端輸出ready接收valid。因此,這兩對信號有如下表所示的對應(yīng)關(guān)系:
| | master | slave | | valid | rdy | en | | ready | en | rdy |
其次,是控制信號之間依賴關(guān)系的差異。在BSV中,method的en信號依賴于rdy信號,具體來說一個method只有在其準備好(rdy為高)時才能被調(diào)用。下圖展示了這一依賴關(guān)系在硬件上的具體實現(xiàn),即輸入的en信號需要和輸出的rdy信號相與后傳遞給下一級。而在AXI協(xié)議中,為了避免產(chǎn)生死鎖(通信雙方都等待對方準備好后再響應(yīng)),其明確規(guī)定:master輸出的valid信號不能依賴于ready信號,即不能等待slave側(cè)準備好后再發(fā)起請求;相反,slave端輸出的ready信號可以依賴于輸入的valid,即可以等待master發(fā)起請求后再作響應(yīng)。
在大部分使用Verilog實現(xiàn)的電路中,我們都會基于valid-ready握手協(xié)議實現(xiàn)模塊之間的交互。如果在一個項目中,我們需要將BSV生成的代碼和基于Verilog的設(shè)計進行交互,通常還需要實現(xiàn)一個轉(zhuǎn)換模塊,來處理valid-ready和en-rdy控制信號之間的交互。
除了控制信號的差異外,BSV生成的Verilog代碼還存在如下問題:如果在BSV中將多個相關(guān)的輸入/輸出信號封裝在一個結(jié)構(gòu)體,那么在生成的Verilog接口中所有封裝在一起的字段都會合并成單個信號。例如,在BSV中使用AXI-Stream總線時,為了方便信號傳遞,通常會將總線上的信號封裝成一個結(jié)構(gòu)體后在方法之間傳遞:
typedef struct {
Bit#(TMul#(keepWidth, 8)) tData;
Bit#(keepWidth) tKeep;
Bit#(usrWidth) tUser;
Bool tLast;
} AxiStream#(numeric type keepWidth, numeric type usrWidth) deriving(Bits);
interface AxiStreamExample;
interface Put#(AxiStream#(8, 1)) axiStreamSlave;
interface Get#(AxiStream#(8, 1)) axiStreamMaster;
endinterface
而在生成的Verilog代碼中,結(jié)構(gòu)體里定義的所有字段都合并到了axiStreamSlave_put/axiStreamMaster_get信號里:
module mkAxiStreamExample(
CLK,
RST_N,
axiStreamSlave_put,
EN_axiStreamSlave_put,
RDY_axiStreamSlave_put,
EN_axiStreamMaster_get,
axiStreamMaster_get,
RDY_axiStreamMaster_get
);
如果我們要將上述代碼與其他Verilog模塊交互,需要添加一個額外的模塊對生成的mkAxiStreamExample進行封裝。該模塊需要完成兩件事:
1)將合并的信號解析成每個獨立的信號,
2)將en-rdy轉(zhuǎn)換為valid-ready握手協(xié)議。
以Master端信號的封裝為例,具體的實現(xiàn)代碼如下:
module mkAxiStreamExampleWrapper(
input clk,
input reset_n,
output m_axis_tvalid,
input m_axis_tready,
output m_axis_tlast,
output m_axis_tuser,
output [63:0] m_axis_tdata,
output [ 7:0] m_axis_tkeep,
);
mkAxiStreamExample axiStreamExampleInst (
.CLK ( clk),
.RST_N (reset_n),
.RDY_axiStreamMaster_get (m_axis_tvalid),
.EN_axiStreamMaster_get (m_axis_tvalid & m_axis_tready),
.axiStreamMaster_get (
{m_axis_tdata, m_axis_tkeep, m_axis_tuser, m_axis_tlast}
)
);
endmodule
雖然上面展示的Verilog封裝模塊可以保證模塊間正確的交互,但仍存在一些缺陷。首先,解析打包信號的方式與BSV中結(jié)構(gòu)體的定義相關(guān),如果結(jié)構(gòu)的內(nèi)容發(fā)生更改,封裝模塊解析出的結(jié)果就可能出錯。其次,手動地處理en-rdy和valid-ready信號對之間的轉(zhuǎn)換也容易出錯。這些問題都降低了BSV項目的可維護性。
為了方便BSV和Verilog之間的交互,我們實現(xiàn)了blue-wrapper項目并提供了等同于上述Verilog封裝的BSV實現(xiàn),使得經(jīng)過封裝的BSV模塊所生成的代碼能夠直接和其他Verilog模塊進行交互。下文將介紹blue-wrapper的具體實現(xiàn)及其使用方式,主要包括三部分內(nèi)容:
- PipeOut/PipeIn接口的定義,對應(yīng)代碼實現(xiàn)見 src/SemiFifo.bsv
- 基于PipeOut/PipeIn和Get/Put接口實現(xiàn)握手控制信號轉(zhuǎn)換, 詳細代碼實現(xiàn)可見 src/BusConversion.bsv;
- 在控制信號轉(zhuǎn)換的基礎(chǔ)上,還需要對完成對數(shù)據(jù)信號的解析,blue-wrapper中分別提供了對AXI-Stream,AXI4-Lite和AXI4-Full等協(xié)議的支持;
下文將結(jié)合實際代碼分別介紹這三部分的具體實現(xiàn)。
02、PipeOut/PipeIn接口
在基于valid-ready控制信號對的數(shù)據(jù)交互場景下,交互雙方可分為Master和Slave。其中,Master端發(fā)起數(shù)據(jù)傳輸,可對應(yīng)BSV中常用的Get接口,而Slave負責(zé)接收數(shù)據(jù),因此可以對應(yīng)BSV中的Put接口。除了Get/Put外,為了方便實現(xiàn)握手控制信號的轉(zhuǎn)換,blue-wrapper中還額外定義了PipeOut/PipeIn接口,這兩個接口分別封裝了FIFOF接口出隊側(cè)(deq)和入隊(enq)側(cè)的方法,具體定義如下:
interface PipeIn#(type dType);
method Action enq(dType data);
method Bool notFull();
endinterface
interface PipeOut#(type dType);
method dType first();
method Action deq();
method Bool notEmpty();
endinterface
從實現(xiàn)功能的角度上看,PipeOut/PipeIn和Get/Put接口類似,都可分別實現(xiàn)數(shù)據(jù)的輸出/輸入。但對于Get/Put接口,其get/put方法所隱含的en-rdy控制信號在BSV中是無法訪問的。而對于PipeOut/PipeIn接口,其將deq/enq方法對應(yīng)的rdy信號分別通過notEmpty/notFull方法暴露出來,使得我們可以直接在BSV中對其進行訪問,而這一點將極大地方便握手控制信號的轉(zhuǎn)換。
03、握手控制信號轉(zhuǎn)換
顯式定義valid-ready信號
實現(xiàn)接口轉(zhuǎn)換的第一步需要在interface中定義valid和ready信號對應(yīng)的method,一方面使得生成的Verilog代碼直接包含valid-ready信號對,另一方面方便我們在BSV中操縱這兩個信號實現(xiàn)握手協(xié)議轉(zhuǎn)換。在BSV中,一個方法的返回值對應(yīng)Verilog的輸出端口,而方法的輸入?yún)?shù)對應(yīng)輸入端口,基于該原則,valid-ready協(xié)議的Master/Slave側(cè)接口的BSV定義如下:
(* always_ready, always_enabled )
interface RawBusMaster#(type dType);
( result = "data" ) method dType data;
( result = "valid"*) method Bool valid;
(* prefix = "" ) method Action ready(( port = "ready" ) Bool rdy);
endinterface
( always_ready, always_enabled )
interface RawBusSlave#(type dType);
( prefix = "" ) method Action validData(
( port = "valid" ) Bool valid,
( port = "data" ) dType data
);
( result = "ready" *) method Bool ready;
endinterface
上述代碼中,編譯屬性“always_ready”和“always_enabled”消除了每個method隱含的en-rdy控制信號對。同時,我們可以通過設(shè)置輸出method的“result”屬性和每個輸入?yún)?shù)的“port”屬性來指定生成的Verilog中每個端口的具體名稱。
en-rdy和valid-ready之間的轉(zhuǎn)換
在定義好包含valid-ready控制信號的interface后,下一步需要完成en-rdy到valid-ready握手控制信號的轉(zhuǎn)換。blue-wrapper項目分別提供了兩種不同的轉(zhuǎn)換思路:
- 將需要封裝的PipeOut/PipeIn接口作為參數(shù)傳入轉(zhuǎn)換模塊供valid/ready信號對應(yīng)的method調(diào)用;
- 將RawBusMaster/Slave接口分別封裝成PipeIn/PipeOut接口供其他BSV模塊調(diào)用;
基于第一種思路實現(xiàn)的轉(zhuǎn)換模塊包括: mkPipeOutToRawBusMaster/mkPipeInToRawBusSlave,以及mkGetToRawBusMaster和mkPutToRawBusSlave。以mkPipeOutToRawBusMaster為例,該模塊接收PipeOut接口作為輸入?yún)?shù),并返回RawBusMaster接口,其中各個method的實現(xiàn)思路和具體代碼如下:
- **valid:**對于Master端,其valid信號對應(yīng)BSV中的rdy信號,而PipeOut接口通過notEmpty方法暴露出了deq方法對應(yīng)的rdy,因此valid方法直接返回notEmpty的值;
- **data:**對應(yīng)PipeOut接口的first方法;
- **ready:**作為方法輸入?yún)?shù)傳遞的ready信號對應(yīng)BSV中的en控制信號,該值為真時需要調(diào)用PipeOut接口的deq方法。由于在BSV中調(diào)用任意method,編譯器都會自動保證上文提到的en-rdy的依賴關(guān)系,因此在調(diào)用deq方法時不需要額外檢查其rdy信號來保證握手成功。
module mkPipeOutToRawBusMaster#(
PipeOut#(dType) pipe
)(RawBusMaster#(dType));
RWire#(dType) dataW < - mkRWire;
Wire#(Bool) readyW < - mkBypassWire;
rule passWire if (pipe.notEmpty);
dataW.wset(pipe.first);
endrule
rule passReady if (readyW);
pipe.deq;
endrule
method Bool valid = pipe.notEmpty;
method dType data = fromMaybe(?, dataW.wget);
method Action ready(Bool rdy);
readyW <= rdy;
endmethod
endmodule
對于PipeIn到RawBusSlave的轉(zhuǎn)換,其實現(xiàn)的原理和Master端類似,具體代碼如下:
module mkPipeInToRawBusSlave#(
PipeIn#(dType) pipe
)(RawBusSlave#(dType));
Wire#(Bool) validW < - mkBypassWire;
Wire#(dType) dataW < - mkBypassWire;
rule passData if (validW);
pipe.enq(dataW);
endrule
method Action validData(Bool valid, dType data);
validW <= valid;
dataW <= data;
endmethod
method Bool ready = pipe.notFull;
endmodule
對于Get/Put接口,由于在BSV中無法直接訪問get/put方法的rdy信號,直接進行轉(zhuǎn)換無法提取出master輸出的valid信號以及slave輸出的ready信號。因此,在blue-wrapper的實現(xiàn)中我們通過添加一個額外的FIFOF模塊作為媒介,將Get/Put接口轉(zhuǎn)換成PipeOut/PipeIn接口后,調(diào)用上面展示的兩個模塊實現(xiàn)控制信號的轉(zhuǎn)換,以Get接口為例,具體的代碼實現(xiàn)如下:
module mkGetToRawBusMaster#(
Get#(dType) get
)(RawBusMaster#(dType));
FIFOF#(dType) fifo < - mkFIFOF;
mkConnection(get, toPut(fifo));
let rawBus < - mkPipeOutToRawBusMaster(
convertFifoToPipeOut(fifo)
);
return rawBus;
endmodule
第二種實現(xiàn)思路對應(yīng)代碼中的: mkRawBusMasterToPut/mkRawBusMasterToGet ,以及mkRawBusMasterToPipeIn/mkRawBusMasterToPipeOut四個模塊。這種轉(zhuǎn)換方式分別用PipeIn/PipeOut或Put/Get接口封裝RawBusMaster/RawBusSlave接口 。 對于其他BSV模塊,可以通過PipeIn/Put接口將數(shù)據(jù)傳入轉(zhuǎn)換模塊然后從RawBusMaster發(fā)送出去,同時可以通過PipeOut/Get接口獲取從RawBusSlave上接收到的數(shù)據(jù)。這種實現(xiàn)方式和BSV提供的BVI接口類似。以master側(cè)為例,具體的轉(zhuǎn)換實現(xiàn)如下:
首先,我們需要定義RawBusMasterToPipeIn接口,其由兩個子接口RawBusMaster和PipeIn組成,其中PipeIn用于封裝RawBusMaster使其可供其他BSV模塊調(diào)用。
對于RawBusMaster接口,其實現(xiàn)代碼主要是在進行信號的傳遞, 從validData中取出valid和data方法的返回值,并將ready方法的輸入?yún)?shù)傳遞給 readyW 。
對于PipeIn接口,notFull方法返回readyW的值,enq方法將傳入的參數(shù)寫入 validData。 同時為了保證握手成功,enq方法需要被readyW所守衛(wèi)(guarded), 即當輸入的ready信號為高時,才可調(diào)用enq方法傳入數(shù)據(jù)。
interface RawBusMasterToPipeIn#(type dType);
interface RawBusMaster#(dType) rawBus;
interface PipeIn#(dType) pipe;
endinterface
module mkRawBusMasterToPipeIn(RawBusMasterToPipeIn#(dType));
RWire#(dType) validData < - mkRWire;
Wire#(Bool) readyW < - mkBypassWire;
interface RawBusMaster rawBus;
method Bool valid = isValid(validData.wget);
method dType data = fromMaybe(?, validData.wget);
method Action ready(Bool rdy);
readyW <= rdy;
endmethod
endinterface
interface PipeIn pipe;
method Bool notFull = readyW;
method Action enq(dType data) if (readyW);
validData.wset(data);
endmethod
endinterface
endmodule
對于Get/Put接口,也可以使用上述方法對RawBusSlave/RawBusMaster接口進行封裝,其實現(xiàn)的關(guān)鍵點都是要為get和put方法設(shè)置正確的守衛(wèi)信號以保證握手成功.
死鎖問題
為了避免master和slave之間互相等待而產(chǎn)生死鎖,AXI文檔中規(guī)定master不能等待slave側(cè)拉高ready后再輸出有效的valid和data,但允許slave側(cè)在master拉高valid之后再置ready為高。在BSV中對于每個method,en只有在rdy信號拉高后才能拉高。由于en-rdy和valid-ready不同的依賴關(guān)系,在交互的過程中就有可能導(dǎo)致雙方產(chǎn)生死鎖。下文將主要針對上面提到的兩種封裝方法,分析其是否會引入死鎖問題。
- 第一種封裝方式相當于是在轉(zhuǎn)換模塊中調(diào)用傳入的PipeOut/PipeIn接口的方法。在BSV中調(diào)用任何方法,編譯器都會自動地為en添加對于rdy的依賴,具體的en-rdy和valid-ready間的交互可以由下圖所示。其中,en-rdy之間的依賴關(guān)系如紅色虛線所示。當Slave側(cè)為Verilog實現(xiàn)時,valid和ready之間可能存在如黑色虛線所示的依賴關(guān)系。由圖可知,這兩種依賴關(guān)系同向因此不會產(chǎn)生死鎖。
- 第二種封裝方式類似于BSV提供的BVI接口,其將Verilog信號封裝成BSV中的method供其他模塊調(diào)用。同樣的,對于這些method,BSV會給en信號添加對rdy的依賴,如下圖紅色虛線所示。而基于Verilog實現(xiàn)的Slave端口,其valid-ready之間可能存在如黑色虛線所示的依賴關(guān)系。由圖可見,en-rdy和valid-ready之間正好形成了一個死鎖環(huán)路,即master端等待ready拉高后輸出有效的valid,而slave等待master輸出有效valid后再拉高ready。因此,在使用mkRawBusMasterToPipeOut轉(zhuǎn)換模塊時需要保證所對接的slave側(cè)Verilog實現(xiàn)中不存在ready對valid的依賴。
04、信號解析
完成控制信號的轉(zhuǎn)換后,我們已經(jīng)實現(xiàn)了可生成valid-ready控制信號的 RawBusMaster#(dType) 和 RawBusSlave#(dType) 接口 。 但是,如果dType是用戶定義的struct結(jié)構(gòu)體,則生成的Verilog會將struct中的所有字段打包到一個信號中。因此,我們?nèi)匀恍枰贐SV中將結(jié)構(gòu)體的每個字段解析為單獨的信號,以生成直接可用的Verilog代碼。blue-wrapper分別提供了針對AXI-Stream, AXI4-Lite以及AXI4-Full三種總線協(xié)議的信號解析實現(xiàn)。以Master端的AXI-Stream接口為例,其信號解析代碼如下:
(*always_ready, always_enabled*)
interface RawAxiStreamMaster#(numeric type dataWidth, numeric type usrWidth);
(* result = "tvalid" *) method Bool tValid;
(* result = "tdata" *) method Bit#(dataWidth) tData;
(* result = "tkeep" *) method Bit#(keepWidth) tKeep;
(* result = "tlast" *) method Bool tLast;
(* result = "tuser" *) method Bit#(usrWidth) tUser;
(* always_enabled, prefix = "" *)
method Action tReady((* port="tready" *) Bool ready);
endinterface
module mkPipeOutToRawAxiStreamMaster#(
PipeOut#(AxiStream#(dataWidth, usrWidth)) pipe
)(RawAxiStreamMaster#(dataWidth, usrWidth));
let rawBus - mkPipeOutToRawBusMaster(pipe);
return convertRawBusToRawAxiStreamMaster(rawBus);
interface RawAxiStreamMaster;
method Bool tValid = rawBus.valid;
method Bit#(dataWidth) tData = rawBus.data.tData;
method Bit#(keepWidth) tKeep = rawBus.data.tKeep;
method Bool tLast = rawBus.data.tLast;
method Bit#(usrWidth) tUser = rawBus.data.tUser;
method Action tReady(Bool rdy);
rawBus.ready(rdy);
endmethod
endinterface
endmodule
除了使用blue-wrapper中提供的三種常用總線接口的轉(zhuǎn)換模塊外,用戶也可以仿照上述代碼為自定義的接口實現(xiàn)對應(yīng)的轉(zhuǎn)換模塊,具體的實現(xiàn)步驟如下:
- 首先,需要實現(xiàn)自定義Verilog接口對應(yīng)的BSV接口。其中,每個輸出信號都需要獨立定義成一個method,每個輸入信號都需定義成Action方法的一個輸入?yún)?shù);
- 使用使用上文的介紹的握手控制信號轉(zhuǎn)換模塊將Get/Put或PipeOut/PipeIn轉(zhuǎn)換成 RawBusMaster/RawBusSlave ;
- 在得到RawBusMaster/RawBusSlave后將結(jié)構(gòu)體中的字段和對應(yīng)的method相連。
05、總結(jié)
對于BSV生成的硬件代碼,其接口通常是基于en-rdy控制信號進行交互,而使用Verilog設(shè)計時,我們通常采用valid-ready信號對實現(xiàn)模塊之間的交互。由于控制信號上的差異,將BSV生成的代碼和Verilog設(shè)計進行交互通常需要額外的轉(zhuǎn)換模塊。針對該問題,blue-wrapper項目為BSV代碼實現(xiàn)了相應(yīng)的封裝模塊,使得封裝后生成的Verilog代碼能夠直接和外部的Verilog代碼進行交互。本文主要介紹了blue-wrapper背后的實現(xiàn)原理,具體包括: 定義PipeOut/PipeIn接口以提取出rdy信號;兩種不同的握手控制信號轉(zhuǎn)換的思路;以及解析struct結(jié)構(gòu)體為每個字段生成獨立的信號等三部分內(nèi)容。
評論
查看更多