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