上篇文章給大家介紹了APB協議相關的知識點,本篇文章通過一個實際的APB slave的設計幫助大家鞏固對APB的掌握。
APB slave設計Spec
其框圖如上圖所示,這里提一嘴,大家在做數字IC設計的時候,都應該像這樣規劃好各個模塊的連接關系,確定好以后再寫代碼。該模塊是一個基于APB協議完成寄存器配置或讀取的設計實例。設計相對比較簡單,但不失為一個很好的學習資料。
上面APB相關的信號都介紹過,這里不再重復介紹,其中的ECOREVNUM的意思是ECO revision number,如果沒有用到ARM的ECO的話,將該信號固定為全0即可。
右邊這個slave_reg實際上就對應我們自己設計的IP的寄存器配置部分。這一部分的接口是native interface,也就是沒有考慮通用性的原生接口。想要通過APB總線對其進行配置,就需要通過slave_interface這個模塊進行協議轉換,進而完成APB協議的傳輸。我們經常能夠看到的apb2reg模塊,實際上也是做了這件事。該APB slave有以下的特點:
- 不支持反壓
- 不支持錯誤傳輸
- 支持4個RW類型的寄存器
- 支持12個RO類型寄存器
- 支持字節選通信號
APB slave設計代碼
apb_slave_interface代碼
下面是slave_interface的關鍵代碼邏輯。我們對其進行分析:- 首先由于不支持反壓和錯誤傳輸,因此將pready固定為1,pslverr固定為0。
- APB傳輸進來的paddr可以直接賦給addr,作為讀寫的地址。
- read_en需要在psel為1且pwrite為0的時候拉高。這實際上是希望在整個讀傳輸過程中都讓read_en信號有效。讀者可能就想問了,讀應該對應著兩個階段嗎?不需要判斷嗎?實際上讀的話,master那邊自己控制好就行了,對于slave而言完全可以在第一拍和第二拍都把rdata提供好,這個是沒有關系的。
- write_en則對應著setup phase,實際上在這個場景中修改write_en的邏輯讓該信號對應著access phase也是可以的,其它的信號直接賦值就可以,應該很好理解。
// APB interface assign pready = 1'b1; //always ready. Can be customized to support waitstate if required. assign pslverr = 1'b0; //always OKAY. Can be customized to support error response if required. // register read and write signal assign addr = paddr; assign read_en = psel & (~pwrite); // assert for whole apb read transfer assign write_en = psel & (~penable) & pwrite; // assert for 1st cycle of write transfer // It is also possible to change the design to perform the write in the 2nd // APB cycle. E.g. // assign write_en = psel & penable & pwrite; // However, if the design generate waitstate, this expression will result // in write_en being asserted for multiple cycles. assign byte_strobe = pstrb; assign wdata = pwdata; assign prdata = rdata;
apb_slave_reg代碼
其關鍵邏輯如下所示,我們對其進行分析:- 首先由于分為RW寄存器和RO寄存器。這里確定寫地址是否在規定區間,同時寫使能是否有效,以及byte_strobe信號,來決定要不要寫,寫哪個字節。
- 讀的話就比較簡單了,當讀使能有效,根據地址信號決定rdata。實際上這就是個MUX選擇邏輯。根據read_en加地址從多個寄存器的Q端選出某一個來。
// Address decoding for write operations
assign wr_sel[0] = ((addr[(ADDRWIDTH-1):2]==10'b0000000000)&(write_en)) ? 1'b1: 1'b0;
assign wr_sel[1] = ((addr[(ADDRWIDTH-1):2]==10'b0000000001)&(write_en)) ? 1'b1: 1'b0;
assign wr_sel[2] = ((addr[(ADDRWIDTH-1):2]==10'b0000000010)&(write_en)) ? 1'b1: 1'b0;
assign wr_sel[3] = ((addr[(ADDRWIDTH-1):2]==10'b0000000011)&(write_en)) ? 1'b1: 1'b0;
// register write, byte enable
// Data register: data0
always @(posedge pclk or negedge presetn)
begin
if (~presetn)
begin
data0 <= {32{1'b0}}; // Reset data 0 to 0x00000000
end
else if (wr_sel[0])
begin
if (byte_strobe[0])
data0[ 7: 0] <= wdata[ 7: 0];
if (byte_strobe[1])
data0[15: 8] <= wdata[15: 8];
if (byte_strobe[2])
data0[23:16] <= wdata[23:16];
if (byte_strobe[3])
data0[31:24] <= wdata[31:24];
end
end
// register read
always @ (read_en or addr or data0 or data1 or data2 or data3 or ecorevnum)
begin
case (read_en)
1'b1:
begin
if (addr[11:4] == 8'h00) begin
case(addr[3:2])
2'b00: rdata = data0;
2'b01: rdata = data1;
2'b10: rdata = data2;
2'b11: rdata = data3;
default: rdata = {32{1'bx}};
endcase
end
else if (addr[11:6] == 6'h3F) begin
case(addr[5:2])
// Peripheral IDs and Component IDs.
// AHB example slave has part number of 818
4'b0100: rdata = ARM_CMSDK_APB4_EG_SLAVE_PID4; // 0xFD0 : PID 4
4'b0101: rdata = ARM_CMSDK_APB4_EG_SLAVE_PID5; // 0xFD4 : PID 5
4'b0110: rdata = ARM_CMSDK_APB4_EG_SLAVE_PID6; // 0xFD8 : PID 6
4'b0111: rdata = ARM_CMSDK_APB4_EG_SLAVE_PID7; // 0xFDC : PID 7
4'b1000: rdata = ARM_CMSDK_APB4_EG_SLAVE_PID0; // 0xFE0 : PID 0 APB Example slave part number[7:0]
4'b1001: rdata = ARM_CMSDK_APB4_EG_SLAVE_PID1; // 0xFE4 : PID 1 [7:4] jep106_id_3_0. [3:0] part number [11:8]
4'b1010: rdata = ARM_CMSDK_APB4_EG_SLAVE_PID2; // 0xFE8 : PID 2 [7:4] revision, [3] jedec_used. [2:0] jep106_id_6_4
4'b1011: rdata ={ARM_CMSDK_APB4_EG_SLAVE_PID3[31:8], ecorevnum[3:0], 4'h0};
// 0xFEC : PID 3 [7:4] ECO rev number, [3:0] modification number
4'b1100: rdata = ARM_CMSDK_APB4_EG_SLAVE_CID0; // 0xFF0 : CID 0
4'b1101: rdata = ARM_CMSDK_APB4_EG_SLAVE_CID1; // 0xFF4 : CID 1 PrimeCell class
4'b1110: rdata = ARM_CMSDK_APB4_EG_SLAVE_CID2; // 0xFF8 : CID 2
4'b1111: rdata = ARM_CMSDK_APB4_EG_SLAVE_CID3; // 0xFFC : CID 3
// Note : Customer changing the design should modify
// - jep106 value (www.jedec.org)
// - part number (customer define)
// - Optional revision and modification number (e.g. rXpY)
4'b0000, 4'b0001,4'b0010,4'b0011: rdata = {32'h00000000}; // default
default: rdata = {32{1'bx}}; // x propagation
endcase
end
else begin
rdata = {32'h00000000}; // default
end
end
1'b0:
begin
rdata = {32{1'b0}};
end
default:
begin
rdata = {32{1'bx}};
end
endcase
end
APB slave mux設計
這里再給大家介紹一下APB slave mux的概念,如下圖所示,基于APB slave mux我們可以快速地將多個apb slave連接在上面。在實際的設計當中都是采用這樣的方式,連接多個slave的。一般我們管這種模塊叫做interconnect,顧名思義,將不同的模塊連接起來。而APB的interconnect只能連接一個master,因此繼續管他叫interconnect感覺差了點意思。所以一般就叫它slave mux了。
其邏輯框圖如上圖所示,非常的簡單啊。就是一個master和多個slave,通過這個額外的DECODE4bit進行16選1。其關鍵代碼如下所示- 根據PSEL是否有效以及DECODE4BIT的值,完成16選1,PSEL0~PSEL15有一個或者0個拉高。
-
PREADYm默認為1,當PSEL為1的時候,根據譯碼結果選擇相應的PREADY信號(當端口沒有使能的時候
en[x] == 0
, 對應的PREADYx
信號不會被選擇)。 - PSLVERR和PRDATA,選中誰就取誰的。
assign PSEL0 = PSEL & dec[ 0] & en[ 0];
assign PSEL1 = PSEL & dec[ 1] & en[ 1];
assign PSEL2 = PSEL & dec[ 2] & en[ 2];
//省略3~15
assign PREADY = ~PSEL |
( dec[ 0] & (PREADY0 | ~en[ 0]) ) |
( dec[ 1] & (PREADY1 | ~en[ 1]) ) |
( dec[ 2] & (PREADY2 | ~en[ 2]) ) |
//省略3~15
assign PSLVERR = ( PSEL0 & PSLVERR0 ) |
( PSEL1 & PSLVERR1 ) |
( PSEL2 & PSLVERR2 ) |
//省略3~15
assign PRDATA = ( {32{PSEL0 }} & PRDATA0 ) |
( {32{PSEL1 }} & PRDATA1 ) |
( {32{PSEL2 }} & PRDATA2 ) |
//省略3~15
這套代碼的缺點或者說優點是,它是用組合邏輯做的,邏輯非常的簡單。實際上就是多選1,一般來說APB的時鐘頻率很低,所以增加了一定的組合邏輯級數也不會出現時鐘違例。這樣做還可以節省一個時鐘周期,大家也可以用時序邏輯去做,思路是類似的。還有一個問題就是這個模塊沒有PENABLE信號,這個其實挺致命,大家可以手動加上,跟PSEL的邏輯基本是一模一樣的,非常簡單。
-
寄存器
+關注
關注
31文章
5363瀏覽量
121171 -
AMBA總線
+關注
關注
0文章
35瀏覽量
9612
原文標題:深入理解AMBA總線 — APB slave設計
文章出處:【微信號:IC修真院,微信公眾號:IC修真院】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論