FPGA項目——基於AMBA匯流排的流水燈控制系統

来源:https://www.cnblogs.com/sjtu-zsj-990702/archive/2023/03/30/17251396.html
-Advertisement-
Play Games

緒論 本文將介紹一個完全用Verilog HDL手寫的AMBA片上系統,項目的主題是設計一個基於AMBA匯流排的流水燈控制系統, 項目中所有數字電路邏輯都將通過Verilog進行RTL設計,不會調用成熟IP核, 然後利用Vivado平臺對RTL模型進行模擬、綜合與佈線,最後在FPGA開發板上進行板級驗 ...


緒論

本文將介紹一個完全用Verilog HDL手寫的AMBA片上系統,項目的主題是設計一個基於AMBA匯流排的流水燈控制系統

項目中所有數字電路邏輯都將通過Verilog進行RTL設計,不會調用成熟IP核,

然後利用Vivado平臺對RTL模型進行模擬、綜合與佈線,最後在FPGA開發板上進行板級驗證。

AMBA是ARM公司推出的一種匯流排架構,目前已非常成熟,在行業內得到廣泛的應用,極具實際應用價值,

本項目涉及了AMBA架構中的AHB協議APB協議

系統包括AHB匯流排、APB匯流排兩個部分,

整個系統的基本架構如下圖所示:

總的來說,由AHB匯流排上的主機————流水燈控制單元(Control Unit),發出控制信號,

進而對APB匯流排上的從機GPIO模塊進行配置,實現對流水燈的流動方式的控制,

本項目是一個完整的AMBA匯流排系統,包括匯流排、主機、從機以及外設,

在各章節中,將會介紹如何利用Verilog語言對以下模塊進行RTL設計:

1. Control Unit模塊

2. AHB2APB Bridge模塊

3. GPIO模塊

希望本項目能給對AMBA匯流排架構感興趣的朋友們一些啟發與幫助。

下麵,先簡要介紹一下AHB/APB匯流排的基本特性和數據傳輸時序:

AHB匯流排協議簡介:

AHB匯流排,全稱為Advanced High performance Bus,屬於AMBA2.0規範中的一部分,

在SoC片上系統中,AHB主要用於一些高性能模塊之間的連接(如CPU、DMA、DSP等),

主要的特性包括:單時鐘邊沿操作、支持突發傳輸、支持多個主控制器、可配置匯流排寬度(32位~128位)、支持位元組、半字、字傳輸。

一個典型的AHB匯流排系統通常以下圖所示的方式進行多主機多從機互聯:

可以看出,Master(主機)這邊的地址信號HADDR和數據信號HWDATA都是廣播的,通過仲裁器arbiter給出的hselx來選擇對應的從機,

而Slave(從機)這邊的數據,則是通過Decoder來解碼給主機這邊的。

當HSELx選中對應的從設備後,就可以開始傳輸了,一個無等待傳輸的時序如下所示:

圖中可以看出,AHB匯流排具有流水線特性,地址周期和數據周期交替進行

APB匯流排協議簡介:

APB,全稱為Advanced Peripheral Bus,主要用於低帶寬的周邊外設之間的連接,例如UART、1284等,

APB匯流排架構不像AHB支持多個Master,在APB裡面唯一的Master就是APB橋。

APB匯流排特性包括:

  1. 兩個時鐘周期傳輸(非流水線,不同於AHB)

  2. 無需等待周期和回應信號

  3. 控制邏輯簡單

  4. 只有四個控制信號

下圖展示了APB匯流排上的一次標準的數據傳輸時序:

其他有關APB匯流排的介紹,將在下文AHB2APB Bridge模塊的設計章節中涉及

1. 系統架構與功能

系統架構:

本項目的系統架構框圖上圖所示,

其中,藍色部分為AHB匯流排以及AHB上的主機從機,粉色部分則是APB匯流排以及APB上的主機從機,

下麵介紹一下匯流排上各個模塊的功能和定義:

  1. 流水燈控制單元(Contro Unit)
    在AHB匯流排上,流水燈控制單元作為Master,
    控制單元發出的AHB匯流排信號將經過AHB2APB Bridge,成為APB信號,
    APB信號會對GPIO模塊的寄存器進行配置,配置後的GPIO就可以對開發板上的按鍵狀態進行觀測,並對LED燈進行控制

  2. AHB轉APB橋(AHB2APB Bridge)
    AHB2APB Bridge作為邏輯連接器,一方面是在AHB的Slave,另一方面也是APB的Master(APB匯流排有且只有這唯一1個Master)。
    弄清這個概念,就可以定義模塊的輸入輸出的埠,以及需要滿足的時序。
    由於AHB匯流排具有流水線特性,而APB沒有,這就需要讓AHB在合適的時候拉低HREADYout添加一個等待周期

  3. 通用輸入輸出模塊(General-purpose Input/Output)
    GPIO模塊作為APB匯流排上的Slave,具有一個APB Slave介面,
    除此之外,也會直接連接到FPGA開發板的按鍵外設和LED燈外設:
    GPIO模塊的輸入為Key[3:0],用於觀測開發板上的按鍵狀態,
    GPIO模塊的輸出為LED[3:0],用於控制開發板上LED燈的亮滅。
    由於本項目用到的FPGA開發板的LED外設為共陽極,因此當引腳輸出低電平時,對應LED將被點亮;反之,LED將會熄滅。

項目中涉及到的外設在開發板上的位置如下圖所示:

流水燈系統工作模式:

  1. 初始靜止模式:所有LED燈熄滅
    上電後/按下reset後,進入該狀態,所有LED燈熄滅,
    此時LED_mode = 4'b0000(LED_mode為Control Unit中流水燈工作模式寄存器,用以控制LED燈閃爍的邏輯)

  2. 工作模式0:普通流水燈模式
    初始靜止模式同時按下KEY0~KEY3進入工作模式0,
    或在工作模式0~3下,按下FPGA開發板上按鍵KEY1進入工作模式0,
    此時LED_mode = 4'b0001,
    在工作模式0下,開發板上的LED燈將按照下圖所示的方式,做有規律的周期性流動:

    LED0~LED4將依次閃爍,每個燈閃爍時長為1s,迴圈周期為4s

  3. 工作模式1:加速流水燈模式
    在工作模式0~3下,按下FPGA開發板上按鍵KEY2,進入該工作狀態,
    此時LED_mode = 4'b0010,
    在工作模式1下,LED燈將加速流動,每個燈閃爍時長為0.5s,迴圈周期為2s:
    !

  4. 工作模式2:心跳模式
    在工作模式0~3下,按下FPGA開發板上按鍵KEY3,進入該工作狀態,
    此時LED_mode = 4'b0100,
    該模式下LED等將模仿心跳的節奏進行閃爍(詳見最後一章中FPGA運行視頻)

  5. 工作模式3:呼吸燈模式
    在工作模式0~3下,按下FPGA開發板上按鍵KEY4,進入該工作狀態,
    該模式下LED將周期性進行逐漸由亮到暗再到亮的變化(周期為4s,其中前2s由暗變數,後2由亮變暗),
    此時LED_mode = 4'b1000,
    由於FPGA開發板上的LED燈亮度無法直接配置,
    該模式是通過調節LED燈的占空比來實現的,占空比越高,小燈的平均功率越大,我們人眼看到的亮度也就越高

2. AHB2APB Bridge模塊設計

首先介紹AHB2APB Bridge模塊的設計方法,

該模塊是AHB與APB匯流排之間的橋梁,負責兩種協議信號的相互轉換,

AHB2APB Bridge的模塊框圖和信號定義如下:

AHB2APB Bridge既是AHB匯流排上的一個Slave,也作為APB匯流排上唯一的Master,

其任務是將來自於AHB匯流排上的信號轉化為APB信號,實現AHB系統和APB的互聯,

那麼如何實現一個可以完成上述功能的Bridge模塊呢?

答案是通過一個有限狀態機(FSM,Finite State Machine)來進行控制,

看看一次典型的AHB到APB信號轉換時序圖(以Read為例):

在T1-T2期間,Bridge作為AHB匯流排上的Slave被HADDR選中,

在T2-T4兩個時鐘周期內,Bridge將AHB的HADDR上的地址Addr1放在APB匯流排的PADDR上,

其中T2-T3,PENABLE尚未被拉高,且HREADY會被Bridge拉低,因為APB的傳輸速度是低於AHB的,需要AHB匯流排上的Master進行等待,

而T3-T4,PENABLE被Bridge使能,數據信息被放上PRDATA並且由Bridge轉交給HRDATA,

我們將T1-T2,T2-T3,T3-T4這個周期分別稱為IDLESETUPENABLE

於是我們得到AHB2APB Bridge的狀態機轉換示意圖:

註意:在T2-T4傳輸Data1後,APB匯流排又在T4-T6兩個周期內完成了Data2的傳輸,

這兩次傳輸之間並沒有經歷IDLE狀態,這表明當AHB匯流排上仍然有傳輸任務時,ENABLE狀態結束後會直接再次跳往SETUP狀態以準備下一個數據的傳輸,

AHB2APB Bridge模塊完整RTL如下:

module AHB2APB_bridge #(

    //HRANS Parameters
    parameter    IDLE   = 2'b00  ,
    parameter    BUSY   = 2'b01  ,
    parameter    SEQ    = 2'b10  ,
    parameter    NONSEQ = 2'b11  ,

    //HRSP Parameters
    parameter    OKAY  = 2'b00   ,
    parameter    ERROR = 2'b01   ,
    parameter    SPLIT = 2'b10   ,
    parameter    RETRY = 2'b11   ,

    //bridge_state Parameters
    parameter    BRIDGE_IDLE   = 2'b00,
    parameter    BRIDGE_SETUP  = 2'b01,
    parameter    BRIDGE_ENABLE = 2'b10,

    //ADDR Parameters
    parameter    ADDR_GPIO_0   = 32'h0000_0000,
    parameter    ADDR_GPIO_1   = 32'h0000_8000
    
) (
    //------------ AHB ------------
    //input
    input iHCLK,
    input iHRESETn,
    input iHSEL,
    input  [1:0]  iHTRANS, 
    input  [3:0]  iHSIZE ,
    input  [2:0]  iHBURST, 
    input         iHWRITE,
    input  [31:0] iHWDATA, 
    input  [31:0] iHADDR ,
    //output
    output        oHREADY,
    output [1:0]  oHRESP ,
    output [31:0] oHRDATA,
    //------------ APB ------------
    //input
    input [31:0] iPRDATA,
    //output
    output oPSEL0,
    output oPSEL1,
    output oPWRITE,
    output oPENABLE,
    output [31:0] oPADDR,
    output [31:0] oPWDATA
);
    /*————————————————————————————————————————————————————————————————————————*\
    /                            AHB Signal Register                           \
    \*————————————————————————————————————————————————————————————————————————*/
    reg         iHWRITE_r ;
    reg  [31:0] iHADDR_r  ;
    reg  [31:0] iHWDATA_r ;

    always@( posedge iHCLK)  begin
        if(!iHRESETn) begin
            iHWRITE_r <= 1'b0;
            iHADDR_r  <= 32'b0;
            iHWDATA_r <= 32'b0;
        end 
        else if( iHSEL && (bridge_state == BRIDGE_IDLE || bridge_state == BRIDGE_ENABLE))begin
            iHWRITE_r <= iHWRITE; // ahb reg change when bridge_state is going to change:
            iHADDR_r  <= iHADDR ; // from IDLE   to SETUP                     
            iHWDATA_r <= iHWDATA; // from ENABLE to SETUP
        end
    end 
    /*————————————————————————————————————————————————————————————————————————*\
    /                                 Bridge FSM                               \
    \*————————————————————————————————————————————————————————————————————————*/
    reg [1:0] bridge_state;

    always @(posedge iHCLK ) begin
        if (!iHRESETn) begin
            bridge_state <= BRIDGE_IDLE;
        end else begin
            case ( bridge_state )
                // IDLE
                BRIDGE_IDLE: begin
                    if(iHSEL) begin
                        bridge_state <= BRIDGE_SETUP;
                    end 
                    else begin
                        bridge_state <= BRIDGE_IDLE;
                    end                     
                end
                // SETUP
                BRIDGE_SETUP: begin
                    bridge_state <= BRIDGE_ENABLE;
                end
                // ENABLE
                BRIDGE_ENABLE: begin
                    if(iHSEL) begin
                        bridge_state <= BRIDGE_SETUP;
                    end 
                    else begin
                        bridge_state <= BRIDGE_IDLE;
                    end
                end
                //DEFAULT
                default: bridge_state <= BRIDGE_IDLE;
            endcase
        end
    end

    /*————————————————————————————————————————————————————————————————————————*\
    /                               AHB Slave Output                           \
    \*————————————————————————————————————————————————————————————————————————*/
    assign oHREADY = ( bridge_state == BRIDGE_SETUP ) ? 1'b0 : 1'b1;
    assign oHRESP  = OKAY ;
    assign oHRDATA = iPRDATA;

    /*————————————————————————————————————————————————————————————————————————*\
    /                               APB Master Output                          \
    \*————————————————————————————————————————————————————————————————————————*/
    assign oPSEL0   = ( iHADDR_r == ADDR_GPIO_0) ? 1'b1 : 1'b0;
    assign oPSEL1   = ( iHADDR_r == ADDR_GPIO_1) ? 1'b1 : 1'b0;
    assign oPWRITE  = iHWRITE_r ;
    assign oPENABLE = ( bridge_state == BRIDGE_ENABLE ) ? 1'b1 : 1'b0;
    assign oPADDR   = iHADDR_r;
    assign oPWDATA  = iHWDATA_r;

endmodule

3. GPIO模塊設計

GPIO(General Purpose I/O),是一種用於對器件的引腳做觀測或控制的外設,

在STM32、ZYNQ等開發中經常被使用到,相信大家並不陌生,

而在本項目中,我們將嘗試自己用Verilog語言,設計一個具有基本功能的GPIO外設,

該模塊具有一個APB Slave介面,

GPIO模塊會被掛在APB匯流排上,實現流水燈控制系統和開發板的外設(LED燈&按鍵)的交互,

模塊的信號定義如下表:

其中oGPIOout[3]連接開發板上LED4引腳,oGPIOout[2]連接LED3引腳,oGPIOout[1]連接LED2引腳,oGPIOout[0]連接LED1引腳,

iGPIOin[3:0]也是按照該順序,依次連接開發板上的4個按鍵:KEY4,KEY3,KEY2,KEY1。

GPIO模塊的功能實現,主要依靠四個32位寄存器:1.DATA_RO寄存器 2.DATA寄存器 3. DIRM寄存器 4. OEN寄存器

主機通過讀配置寫這些寄存器,就可以實現對外設的操作,

下麵分析這四個寄存器:

DATA_RO
用來觀測GPIO引腳狀態,若引腳被配置成輸出模式,則該寄存器會反映驅動該引腳的電平的狀態。
DATA_RO是一個只讀寄存器,對該寄存器的寫操作是無效的

DATA
當GPIO某一引腳被配置為輸出模式時,用來控制該引腳的輸出狀態

DIRM
用來配置GPIO各個引腳的方向(做輸入or做輸出),
當DIRMP[x]==0,第x位引腳為輸入引腳,其輸出功能被disable

OEN
當GPIO某一引腳被配置為輸出模式時,用來使能該引腳的輸出功能,
當OEN[x]==0時,第x位引腳的輸出功能被disable

為了避免在設計中引入雙向埠,我們為GPIO模塊賦予了一個32位輸入埠iGPIOin[31:0],一個32位輸出埠oGPIOout[31:0],

  1. reg_DIRM[i]=0
    對應i號引腳被DIRM寄存器配置為輸入埠,
    oGPIOout[i]呈現高阻態1'bz,該位對應的引腳輸出功能實際上是不存在的,
    iGPIOin[i]將寫入reg_DATA_RO[i],此時APB匯流排可以通過PRDATA[i]瞭解該引腳的輸入狀態:
    reg [31:0] oGPIOout ;
    always @(*) begin
        for ( i=0 ; i<32 ; i=i+1 ) begin
            if( reg_DIRM[i] & reg_OEN[i] ) begin //output mode
                oGPIOout[i] = reg_DATA[i] ;
            end else begin
                oGPIOout[i] = 1'bz;
            end
        end
    end
  1. 反之,當reg_DIRM[i]=1
    該引腳被DIRM寄存器配置為輸出埠,
    oGPIOout[i]的值取決於reg_DATA[i]的情況,
    iGPIOin[i]該位對應的引腳輸入功能實際上是不存在的,寫入reg_DATA_RO[i]的將是oGPIOout[i](即反映驅動該引腳的電平的狀態):
            for ( i=0 ; i<32 ; i=i+1 ) begin
                if ( reg_DIRM[i] ) begin
                    reg_DATA_RO[i] <= oGPIOout[i] ;// output mode
                end else begin
                    reg_DATA_RO[i] <= iGPIOin[i] ;// input mode      
                end  
            end  

GPIO的輸出邏輯如下圖所示,

只有DIRM寄存器被配置為輸出模式OEN寄存器被配置為輸出使能狀態,oGPIOout才會反應DATA寄存器的狀態,否則為高阻態

輸入引腳的邏輯則如圖中藍色部分所示,DIRM寄存器被配置為輸入模式時將寫入只讀寄存器DATA_RO,

否則DATA_RO取決於當前輸出模式的引腳的輸出狀態

GPIO模塊的完整RTL如下:

module APB_GPIO #(

  //ADDR Parameters
  parameter    ADDR_GPIO   = 32'h0000_0000,
  parameter    OFFSET_GPIO_DATA_RO = 4'h0,
  parameter    OFFSET_GPIO_DATA    = 4'h4,
  parameter    OFFSET_GPIO_DIRM    = 4'h8,
  parameter    OFFSET_GPIO_OEN     = 4'hC

) (
    // APB Signal
    input         iPCLK   ,
    input         iPRESETn,
    input         iPSEL   ,
    input         iPWRITE ,
    input         iPENABLE,
    input  [31:0] iPADDR  ,
    input  [31:0] iPWDATA ,
    output [31:0] oPRDATA , 

    // I/O Signal
    input  [31:0] iGPIOin,
    output [31:0] oGPIOout

);

    /*————————————————————————————————————————————————————————————————————————*\
    /                            APB Signal Register                           \
    \*————————————————————————————————————————————————————————————————————————*/
    reg         iPSELx_r  ;
    reg         iPWRITE_r ;
    reg  [31:0] iPADDR_r  ;
    reg  [31:0] iPWDATA_r ;

    always@( posedge iPCLK)  begin
        if(!iPRESETn) begin
            iPWRITE_r  <= 1'b0; 
            iPADDR_r   <= 16'b0; 
        end
        else begin
            iPWRITE_r  <= iPWRITE; 
            iPWDATA_r  <= iPWDATA; 
            iPADDR_r   <= iPADDR; 
        end
    end

    /*————————————————————————————————————————————————————————————————————————*\
    /                           GPIO Register Declaration                      \
    \*————————————————————————————————————————————————————————————————————————*/
    // Read Only Data
    reg [31:0] reg_DATA_RO;
    // GPIO Data 
    reg [31:0] reg_DATA;
    // Direction (in or out)
    reg [31:0] reg_DIRM;
    // Output Enable 
    reg [31:0] reg_OEN;

    /*————————————————————————————————————————————————————————————————————————*\
    /                              Register Configuration                      \
    \*————————————————————————————————————————————————————————————————————————*/
    integer i;
    //reg [31:0] GPIOin_r;

    always @(posedge iPCLK ) begin
        if( !iPRESETn ) begin
            reg_DATA_RO   <= 32'b0;
            reg_DATA      <= 32'b0;
            reg_DIRM      <= 32'b0;
            reg_OEN       <= 32'b0;
        end
        else begin
     
            // reg_DATA, reg_DIRM, reg_OEN
            if( iPENABLE && iPWRITE) begin
                case ( iPADDR[3:0] )
                    OFFSET_GPIO_DATA_RO: begin end    //DATA_RO is read only register             
                    OFFSET_GPIO_DATA:    begin
                        reg_DATA       <= iPWDATA;
                    end
                    OFFSET_GPIO_DIRM:    begin
                        reg_DIRM <= iPWDATA;
                    end
                    OFFSET_GPIO_OEN:     begin
                        reg_OEN <= iPWDATA;
                    end
                    default:             begin
                        reg_DATA    <= reg_DATA   ;
                        reg_DIRM    <= reg_DIRM   ;
                        reg_OEN     <= reg_OEN    ;
                    end
                endcase 
            end

            // DATA_RO
            for ( i=0 ; i<32 ; i=i+1 ) begin
                if ( reg_DIRM[i] ) begin
                    reg_DATA_RO[i] <= oGPIOout[i] ;// output mode
                end else begin
                    reg_DATA_RO[i] <= iGPIOin[i] ;// input mode      
                end  
            end     
       end
    end

    /*————————————————————————————————————————————————————————————————————————*\
    /                                     I/O                                  \
    \*————————————————————————————————————————————————————————————————————————*/
    // iGPIOin -> GPIOin_r -> DATA_RO -> PRADATA
    assign oPRDATA = reg_DATA_RO;

    // reg_DATA -> GPIOout 
    reg [31:0] oGPIOout ;
    always @(*) begin
        for ( i=0 ; i<32 ; i=i+1 ) begin
            if( reg_DIRM[i] & reg_OEN[i] ) begin //output mode
                oGPIOout[i] = reg_DATA[i] ;
            end else begin
                oGPIOout[i] = 1'bz;
            end
        end
    end

endmodule

4. Control Unit(流水燈控制單元)模塊設計

4.1 有限狀態機設計

我們的AMBA流水燈控制系統的核心:Control Unit,即控制單元,

Bridge模塊是APB上所有外設的Master,而CU模塊則是Bridge在AHB上的Master,

可以說是Master中的Master了,

Control Unit是信號傳輸的發起者,決定匯流排上是在讀還是在寫、也決定訪問的從機是哪個,

CU發出的控制信號包括HTRANS、HBURST、HSIZE、HADDR,數據信號包括HWDATA,

這些AHB信號如何生成均是由Control Unit負責的,

同時CU也需要接收HRDATA,並對其進行解讀判斷,

那麼這些AHB信號是如何生成的呢?

實際上,Control Unit模塊也是通過一個有限狀態機實現的:

下麵介紹一下該有限狀態機各個狀態的含義:

CONFIG_0~2:按下複位按鈕/上電後,對GPIO外設進行初始化配置,

其中,
CONFIG_0
將GPIO的0~3位引腳配置為輸入模式,連接FPGA開發板的Key按鍵進行觀測
將GPIO的4~7位引腳配置為輸出模式,連接FPGA開發板的LED燈進行驅動控制

CONFIG_1
將GPIO的4~7位引腳的輸出電平均設定為高電平1(本開發板上的LED為共陽極,引腳高電平=LED燈熄滅)

CONFIG_2
對GPIO的4~7位引腳進行輸出使能

READ_DATA_RO
讀GPIO的DATA_RO寄存器,根據寄存器[3:0]的值得知Key狀態,
該狀態是FSM的核心狀態,
當配置完GPIO的DIRM、OEN、DATA寄存器後,
我們會開始反覆進行讀DATA_RO,即觀測KEY的狀態,
然後根據KEY的值得到對應的LED_mode,
隨後會由該狀態跳向WRITE_DATA_0~3,對DATA寄存器進行配置,控制LED燈點亮or熄滅

WRITE_DATA_0~3
配置不同的流水燈燈工作模式,模式由KEY狀態決定,
按下KEY1進入工作模式0,按下KEY2進入工作模式1,按下KEY3進入工作模式2,按下KEY4進入工作模式3,
在初始靜止狀態下,只有同時按下KEY0~3才能進入工作模式0,
此時按下某個單獨的KEY不會有任何反應的

隨著FSM狀態的改變,CU模塊將會給AHB匯流排發出不同的讀寫命令,從而實現對GPIO模塊的寄存器配置,

下表具體地整理了FSM狀態CU模塊生成的AHB信號之間的對應關係:

上述FSM狀態轉換邏輯所對應的RTL:

reg [2:0] CU_state;

    always @(posedge iHCLK ) begin
    if ( !iHRESETn ) begin
        CU_state <= CONFIG_0;
    end else begin
        case (CU_state)
            // write GPIO_DIRM 
            CONFIG_0: begin
                if ( iHREADY ) begin
                    CU_state <= CONFIG_1;
                end else begin
                    CU_state <= CONFIG_0;
                end
            end
            // write GPIO_DATA
            CONFIG_1: begin
                if ( iHREADY ) begin
                    CU_state <= CONFIG_2;
                end else begin
                    CU_state <= CONFIG_1;
                end
            end 
            // write GPIO_OEN 
            CONFIG_2: begin
                if ( iHREADY && iHRDATA[3:0] == 4'b0000) begin
                    CU_state <= READ_DATA_RO;
                end else begin
                    CU_state <= CONFIG_2;
                end
            end 
            // read DATA_RO
            READ_DATA_RO: begin
                if ( iHREADY          && iHRDATA[3:0] == 4'b1110) begin //key1 pressed
                    CU_state <= WRITE_DATA_0;
                end else if ( iHREADY && iHRDATA[3:0] == 4'b1101) begin //key2 pressed
                    CU_state <= WRITE_DATA_1;
                end else if ( iHREADY && iHRDATA[3:0] == 4'b1011) begin //key3 pressed
                    CU_state <= WRITE_DATA_2;
                end else if ( iHREADY && iHRDATA[3:0] == 4'b0111) begin //key4 pressed
                    CU_state <= WRITE_DATA_3;
                end else if ( iHREADY && led_mode[0] )            begin //keep mode0 if not pressed this moment
                    CU_state <= WRITE_DATA_0;             
                end else if ( iHREADY && led_mode[1] )            begin //keep mode1
                    CU_state <= WRITE_DATA_0;             
                end else if ( iHREADY && led_mode[2] )            begin //keep mode2 
                    CU_state <= WRITE_DATA_1;             
                end else if ( iHREADY && led_mode[3] )            begin //keep mode3
                    CU_state <= WRITE_DATA_2;             
                end else begin
                    CU_state <= READ_DATA_RO;     // Slave not ready || no key ever been pressed
                end                             
            end 
            // write DATA
            WRITE_DATA_0 : begin
                if ( iHREADY ) begin
                    CU_state <= READ_DATA_RO;
                end else begin
                    CU_state <= CU_state;
                end
            end 
            // write DATA
            WRITE_DATA_1 : begin
                if ( iHREADY ) begin
                    CU_state <= READ_DATA_RO;
                end else begin
                    CU_state <= CU_state;
                end
            end 
            // write DATA
            WRITE_DATA_2 : begin
                if ( iHREADY ) begin
                    CU_state <= READ_DATA_RO;
                end else begin
                    CU_state <= CU_state;
                end
            end
            WRITE_DATA_3 : begin
                if ( iHREADY ) begin
                    CU_state <= READ_DATA_RO;
                end else begin
                    CU_state <= CU_state;
                end
            end 
            default: CU_state <= CONFIG_0;
        endcase
        end
    end

LED燈的亮滅改變,是通過配置GPIO內的寄存器實現的,

具體來說,是藉助匯流排信號HWDATA對GPIO的reg_DATA[7:4]寄存器進行配置,

在緒論章節中,我們介紹過LED_mode的編碼方式:

    // LED_mode Parameters
    parameter    MODE0     = 4'b0001,
    parameter    MODE1     = 4'b0010,
    parameter    MODE2     = 4'b0100,
    parameter    MODE3     = 4'b1000,

LED_mode採用了One-hot編碼,這是為了對HWDATA進行assign賦值的時候,判斷條件寫起來簡便一些,

現在我們就可以設計出各種流水燈模式下的組合邏輯了,

各模式下的LED亮滅隨時間改變是通過計時器輔助的,

也就是下麵代碼中的timer,timer是一個32位寄存器:

reg  [31:0]  timer   ;
always @ (posedge iHCLK or negedge iHRESETn)    begin
        if ( !iHRESETn )                           
            timer <= 32'd0;                     // when the reset signal valid,time counter clearing
        else if (timer == 32'd199_999_999)      // 4 seconds count(50M*4-1=199999999)
            timer <= 32'd0;                     // count done,clearing the time counter
        else
		    timer <= timer + 1'b1;              // timer counter = timer counter + 1
    end

本質是將每一秒分成了50M幀(系統工作時鐘頻率為50MHz),

我們用assign語句給不同的幀下的HWDATA賦不同的值就行了,

首先是普通流水燈模式(工作模式0):
(註意,下麵的LED[3:0]在WRITE_DATA_0~3狀態下將會作為HWDATA[7:4]對GPIO_DATA寄存器進行配置)

assign LED[3:0] =         
        // mode0 普通流水燈模式
        ( led_mode[0] && timer >= 32'd149_999_999 ) ? 4'b0111 : // LED4亮 
        ( led_mode[0] && timer >= 32'd99_999_999  ) ? 4'b1011 : // LED3亮
        ( led_mode[0] && timer >= 32'd49_999_999  ) ? 4'b1101 : // LED2亮
        ( led_mode[0]                             ) ? 4'b1110 : // LED1亮  
....(其他模式)

這裡就體現出One-hot編碼的好處了,可以用( led_mode[0] )作為是否處於模式0的判定,
否則需要寫成( led_mode == MODE_0 ),

接下來是加速流水燈模式(工作模式1):

assign LED[3:0] =         
        // mode1 加速流水燈模式
        ( led_mode[1] && timer >= 32'd174_999_999 ) ? 4'b0111 : // LED4亮 
        ( led_mode[1] && timer >= 32'd149_999_999 ) ? 4'b1011 : // LED3亮
        ( led_mode[1] && timer >= 32'd124_999_999 ) ? 4'b1101 : // LED2亮
        ( led_mode[1] && timer >= 32'd99_999_999  ) ? 4'b1110 : // LED1亮
        ( led_mode[1] && timer >= 32'd74_999_999  ) ? 4'b0111 : // LED4亮  
        ( led_mode[1] && timer >= 32'd49_999_999  ) ? 4'b1011 : // LED3亮
        ( led_mode[1] && timer >= 32'd24_999_999  ) ? 4'b1101 : // LED2亮
        ( led_mode[1]                             ) ? 4'b1110 : // LED1亮   
....(其他模式)

心跳模式(工作模式2):

assign LED[3:0] =     
        // mode2 心跳模式
        ( led_mode[2] && timer >= 32'd189_999_999 ) ? 4'b0000 : // 全亮 
        ( led_mode[2] && timer >= 32'd179_999_999 ) ? 4'b1111 : // 全滅
        ( led_mode[2] && timer >= 32'd169_999_999 ) ? 4'b0000 : // 全亮
        ( led_mode[2]                             ) ? 4'b1111 : // 全滅
....(其他模式)

最後是呼吸燈模式(工作模式3),

該模式相對複雜一些,前面提到過是通過改變占空比控制LED燈亮度的,

占空比的改變是通過對timer的判斷實現的,

舉個例子:

assign LED[3:0] = 
        // 占空比 1/4  = 25%
        ( led_mode[3] && (timer >= 32'd89_999_999 )  && (timer[1:0] == 2'b00 )  ) ? 4'b0000 :
        ( led_mode[3] && (timer >= 32'd89_999_999 )                             ) ? 4'b1111 : 
....(其他模式)

(timer[1:0] == 2'b00 )的判定,在timer的值每增加4的過程中,必定出現且只出現1次

這樣在 (timer >= 32'd89_999_999 )的這段期間內,

LED燈的驅動引腳就會在25%的時間里處於低電平(點亮),剩下的75%時間里處於高電平(熄滅),

同理,(timer[2:0] == 3'b0 )就能得到1/8的占空比,也就是12.5%,

這樣就有:

assign LED[3:0] = 
        // mode3 呼吸燈模式
        // 占空比 0%
        ( led_mode[3] && (timer >= 32'd189_999_999)                             ) ? 4'b1111 : 
        // 占空比 1/64 = 1.56%  
        ( led_mode[3] && (timer >= 32'd169_999_999)  && (timer[5:0] == 6'b000)  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd169_999_999)                             ) ? 4'b1111 : 
        // 占空比 1/32 = 3.12%
        ( led_mode[3] && (timer >= 32'd149_999_999)  && (timer[4:0] == 5'b000)  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd149_999_999)                             ) ? 4'b1111 : 
        // 占空比 1/16 = 6.25%
        ( led_mode[3] && (timer >= 32'd129_999_999)  && (timer[3:0] == 4'b000)  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd129_999_999)                             ) ? 4'b1111 : 
        // 占空比 1/8  = 12.5%
        ( led_mode[3] && (timer >= 32'd109_999_999)  && (timer[2:0] == 3'b000)  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd109_999_999)                             ) ? 4'b1111 : 
        // 占空比 1/4  = 25%
        ( led_mode[3] && (timer >= 32'd89_999_999 )  && (timer[1:0] == 2'b00 )  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd89_999_999 )                             ) ? 4'b1111 : 
        // 12.5% 
        ( led_mode[3] && (timer >= 32'd69_999_999 )  && (timer[2:0] == 3'b000)  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd69_999_999 )                             ) ? 4'b1111 : 
        // 6.25% 
        ( led_mode[3] && (timer >= 32'd49_999_999 )  && (timer[3:0] == 4'b000)  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd49_999_999 )                             ) ? 4'b1111 : 
        // 3.12%
        ( led_mode[3] && (timer >= 32'd29_999_999 )  && (timer[4:0] == 5'b000)  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd29_999_999 )                             ) ? 4'b1111 : 
        // 1.56% 
        ( led_mode[3] && (timer >= 32'd9_999_999  )  && (timer[5:0] == 6'b000)  ) ? 4'b0000 :
        ( led_mode[3] && (timer >= 32'd9_999_999  )                             ) ? 4'b1111 : 
        // 0%
        ( led_mode[3]                                                           ) ? 4'b1111 : 
....(其他模式)

可以看出上述代碼實現了占空比從0% → 1.56% → 3.12% → 6.25% → 12.5% → 25% → 12.5% → 6.25% → 3.12% → 1.56% → 0%的等時間間隔變化,

由於開發板LED燈功率較高,在25%到100%之間的占空比,LED燈亮度都會很高,看不出太大變化,

因此我們將25%設定為了最高的占空比,對應LED燈最亮的時刻。

Control Unit模塊的完整RTL如下:

    /*————————————————————————————————————————————————————————————————————————*\
    /                           Control Unit FSM                               \
    \*————————————————————————————————————————————————————————————————————————*/
    
    reg [2:0] CU_state;

    always @(posedge iHCLK ) begin
    if ( !iHRESETn ) begin
        CU_state <= CONFIG_0;
    end else begin
        case (CU_state)
            // write GPIO_DIRM 
            CONFIG_0: begin
                if ( iHREADY ) begin
                    CU_state <= CONFIG_1;
                end else begin
                    CU_state <= CONFIG_0;
                end
            end
            // write GPIO_DATA
            CONFIG_1: begin
                if ( iHREADY ) begin
                    CU_state <= CONFIG_2;
                end else begin
                    CU_state <= CONFIG_1;
                end
            end 
            // write GPIO_OEN 
            CONFIG_2: begin
                if ( iHREADY && iHRDATA[3:0] == 4'b0000) begin
                    CU_state <= READ_DATA_RO;
                end else begin
                    CU_state <= CONFIG_2;
                end
            end 
            // read DATA_RO
            READ_DATA_RO: begin
                if ( iHREADY          && iHRDATA[3:0] == 4'b1110) begin //key1 pressed
                    CU_state <= WRITE_DATA_0;
                end else if ( iHREADY && iHRDATA[3:0] == 4'b1101) begin //key2 pressed
                    CU_state <= WRITE_DATA_1;
                end else if ( iHREADY && iHRDATA[3:0] == 4'b1011) begin //key3 pressed
                    CU_state <= WRITE_DATA_2;
                end else if ( iHREADY && iHRDATA[3:0] == 4'b0111) begin //key4 pressed
                    CU_state <= WRITE_DATA_3;
                end else if ( iHREADY && led_mode[0] )            begin //keep mode0 if not pressed this moment
                    CU_state <= WRITE_DATA_0;             
                end else if ( iHREADY && led_mode[1] )            begin //keep mode1
                    CU_state <= WRITE_DATA_1;             
                end else if ( iHREADY && led_mode[2] )            begin //keep mode2 
                    CU_state <= WRITE_DATA_2;             
                end else if ( iHREADY && led_mode[3] )            begin //keep mode3
                    CU_state <= WRITE_DATA_3;             
                end else begin
                    CU_state <= READ_DATA_RO;     // Slave not ready || no key ever been pressed
                end                             
            end 
            // write DATA
            WRITE_DATA_0 : begin
                if ( iHREADY ) begin
                    CU_state <= READ_DATA_RO;
                end else begin
                    CU_state <= CU_state;
                end
            end 
            // write DATA
            WRITE_DATA_1 : begin
                if ( iHREADY ) begin
                    CU_state <= READ_DATA_RO;
                end else begin
                    CU_state <= CU_state;
                end
            end 
            // write DATA
            WRITE_DATA_2 : begin
                if ( iHREADY ) begin
                    CU_state <= READ_DATA_RO;
                end else begin
                    CU_state <= CU_state;
                end
            end
            WRITE_DATA_3 : begin
                if ( iHREADY ) begin
                    CU_state <= READ_DATA_RO;
                end else begin
                    CU_state <= CU_state;
                end
            end 
            default: CU_state <= CONFIG_0;
        endcase
        end
    end

    /*————————————————————————————————————————————————————————————————————————*\
    /                                AHB Master Output                         \
    \*————————————————————————————————————————————————————————————————————————*/
    
    assign oHTRANS = NONSEQ;
    assign oHBURST = SINGLE;
    assign oHSIZE  = DWORD; 
    assign oHADDR = ( CU_state == CONFIG_0    )? 32'h0000_0008 : 
                    ( CU_state == CONFIG_1    )? 32'h0000_000C : 
                    ( CU_state == CONFIG_2    )? 32'h0000_0004 : 
                    ( CU_state == READ_DATA_RO)? 32'h0000_0000 : 
                    ( CU_state == WRITE_DATA_0)? 32'h0000_0004 : 
                    ( CU_state == WRITE_DATA_1)? 32'h0000_0004 : 
                    ( CU_state == WRITE_DATA_2)? 32'h0000_0004 : 
                    ( CU_state == WRITE_DATA_3)? 32'h0000_0004 :    
                                                 32'hz;
    
    assign oHWRITE = ( CU_state == READ_DATA_RO )? 1'b0 : 1'b1;
    
    assign oHWDATA = ( CU_state == CONFIG_0        )? 32'h0000_00F0          : 
                     ( CU_state == CONFIG_1        )? 32'h0000_00F0          : 
                     ( CU_state == CONFIG_2        )? 32'h0000_00F0          : // all LED turned OFF
                     ( CU_state == WRITE_DATA_0 || CU_state == WRITE_DATA_1 ||
                       CU_state == WRITE_DATA_2 || CU_state == WRITE_DATA_3 ) ? {16'b0,LED[3:0],4'b0 } : 32'hz;

    /*————————————————————————————————————————————————————————————————————————*\
    /                              LED Control                                 \
    \*————————————————————————————————————————————————————————————————————————*/
    
    reg  [3 :0]  led_mode; //4 modes in all (one hot encoding)
    reg  [31:0]  timer   ;
    wire [3 :0]  LED     ;
    
    //------------ led_mode ------------
    always @(posedge iHCLK ) begin
    if ( !iHRESETn ) begin
        led_mode <= 4'b0000;
    end else begin
        case (CU_state)

            CONFIG_0: begin 
                led_mode <= 4'b0000; 
            end

            CONFIG_1: begin
                led_mode <= 4'b0000;
            end 

            CONFIG_2: begin 
                if ( iHREADY && iHRDATA[3:0] == 4'b0000) begin
                    led_mode <= MODE0;
                end else begin
                    led_mode <= 4'b0000;
                end 
            end

            READ_DATA_RO: begin
                // key1 pressed mode -> MODE0 
                if( iHREADY          && iHRDATA[3:0] == 4'b1110) begin
                    led_mode <= MODE0;  
                // key2 pressed mode -> MODE1 
                end else if( iHREADY && iHRDATA[3:0] == 4'b1101) begin
                    led_mode <= MODE1; 
                // key3 pressed mode -> MODE2 
                end else if( iHREADY && iHRDATA[3:0] == 4'b1011) begin
                    led_mode <= MODE2; 
                // key4 pressed mode -> MODE3 
                end else if( iHREADY && iHRDATA[3:0] == 4'b0111) begin
                    led_mode <= MODE3; 
                // no key pressed, mode kept 
                end else begin
                    led_mode <= led_mode;
                end                      
            end 

            WRITE_DATA_0 : begin
                led_mode <= led_mode;
            end 
            WRITE_DATA_1 : begin
                led_mode <= led_mode;
            end 
            WRITE_DATA_2 : begin
                led_mode <= led_mode;
            end 
            WRITE_DATA_3 : begin
                led_mode <= led_mode;
            end 
            default: begin 
                led_mode <= led_mode;
            end

        endcase
    end
    end

    //------------ timer ------------
    always @ (posedge iHCLK or negedge iHRESETn)    begin
        if ( !iHRESETn )                           
            timer <= 32'd0;                     // when the reset signal valid,time counter clearing
        else if (timer == 32'd199_999_999)      // 4 seconds count(50M*4-1=199999999)
            timer <= 32'd0;                     // count done,clearing the time counter
        else
		    timer <= timer + 1'b1;              // timer counter = timer counter + 1
    end

    //------------ led ------------ 
    assign LED[3:0] =         
        // mode0 普通流水燈模式
        ( led_mode[0] && timer >= 32'd149_999_999 ) ? 4'b0111 : // LED4亮 
        ( led_mode[0] && timer >= 32'd99_999_999  ) ? 4'b1011 : // LED3亮
        ( led_mode[0] && timer >= 32'd49_999_999  ) ? 4'b1101 : // LED2亮
        ( led_mode[0]                             ) ? 4'b1110 : // LED1亮       
        // mode1 加速流水燈模式
        ( led_mode[1] && timer >= 32'd174_999_999 ) ? 4'b0111 : // LED4亮 
        ( led_mode[1] && timer >= 32'd149_999_999 ) ? 4'b1011 : // LED3亮
        ( led_mode[1] && timer >= 32'd124_999_999 ) ? 4'b1101 : // LED2亮
        ( led_mode[1] && timer >= 32'd99_999_999  ) ? 4'b1110 : // LED1亮
        ( led_mode[1] && timer >= 32'd74_999_999  ) ? 4'b0111 : // LED4亮  
        ( led_mode[1] && timer >= 32'd49_999_999  ) ? 4'b1011 : // LED3亮
        ( led_mode[1] && timer >= 32'd24_999_999  ) ? 4'b1101 : // LED2亮
        ( led_mode[1]                             ) ? 4'b1110 : // LED1亮
        // mode2 心跳模式
        ( led_mode[2] && timer >= 32'd189_999_999 ) ? 4'b0000 : // 全亮 
        ( led_mode[2] && timer >= 32'd179_999_999 ) ? 4'b1111 : // 全滅
        ( led_mode[2] && timer >= 32'd169_999_999 ) ? 4'b0000 : // 全亮
        ( led_mode[2]                             ) ? 4'b1111 : // 全滅
        // mode3 呼吸燈模式
        // 占空比 0%
        ( led_mode[3] && (timer >= 32'd189_999_999)                             ) ? 4'b1111 : 
        // 占空比 1/64 = 1.56%  
        ( led_mode[3] && (timer >= 32'd169_999_999)  && (timer[5:0] == 6'b000)  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd169_999_999)                             ) ? 4'b1111 : 
        // 占空比 1/32 = 3.12%
        ( led_mode[3] && (timer >= 32'd149_999_999)  && (timer[4:0] == 5'b000)  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd149_999_999)                             ) ? 4'b1111 : 
        // 占空比 1/16 = 6.25%
        ( led_mode[3] && (timer >= 32'd129_999_999)  && (timer[3:0] == 4'b000)  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd129_999_999)                             ) ? 4'b1111 : 
        // 占空比 1/8  = 12.5%
        ( led_mode[3] && (timer >= 32'd109_999_999)  && (timer[2:0] == 3'b000)  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd109_999_999)                             ) ? 4'b1111 : 
        // 占空比 1/4  = 25%
        ( led_mode[3] && (timer >= 32'd89_999_999 )  && (timer[1:0] == 2'b00 )  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd89_999_999 )                             ) ? 4'b1111 : 
        // 12.5% 
        ( led_mode[3] && (timer >= 32'd69_999_999 )  && (timer[2:0] == 3'b000)  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd69_999_999 )                             ) ? 4'b1111 : 
        // 6.25% 
        ( led_mode[3] && (timer >= 32'd49_999_999 )  && (timer[3:0] == 4'b000)  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd49_999_999 )                             ) ? 4'b1111 : 
        // 3.12%
        ( led_mode[3] && (timer >= 32'd29_999_999 )  && (timer[4:0] == 5'b000)  ) ? 4'b0000 : 
        ( led_mode[3] && (timer >= 32'd29_999_999 )                             ) ? 4'b1111 : 
        // 1.56% 
        ( led_mode[3] && (timer >= 32'd9_999_999  )  && (timer[5:0] == 6'b000)  ) ? 4'b0000 :
        ( led_mode[3] && (timer >= 32'd9_999_999  )                             ) ? 4'b1111 : 
        // 0%
        ( led_mode[3]                                                           ) ? 4'b1111 :  4'b1111 ; 
          
endmodule

至此,Control Unit的設計就介紹完了,下麵我們將演示系統在FPGA開發板上的實際運行視頻

5. FPGA驗證

本項目所用開發板型號:XC7A35T-2FGG484I

上板驗證視頻:https://www.bilibili.com/video/BV1EM4y1U7QP/?vd_source=b96e62cf611b2bbbbacda9f1c4d9a394


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 近些年來,隨著手機技術迭代更新越來越快,用戶更換手機的周期也在縮短,在這樣的背景下,開發者不得不面臨以下問題: 同一開發者旗下常常有多個安卓應用和多形態應用(快應用和Web應用),用戶更換一個新的設備(手機或平板)後,在新設備上登錄各應用時每次都需要重覆輸入帳號和密碼,導致用戶在登錄階段流失率增加, ...
  • 這是一款使用 ChatGPT API 進行劃詞翻譯和文本潤色的瀏覽器插件。藉助了 ChatGPT 強大的翻譯能力,它將幫助您更流暢地閱讀外語和編輯外語。 它能幹啥 一. 可翻譯 二. 可潤色 三. 可總結 四. 可分析 五. 可解釋程式代碼 插件地址 OpenAI Translator 勸退聲明 由 ...
  • 一句話來解釋什麼是深淺拷貝,B拷貝A,當修改A,B如果變化,就是淺拷貝,反之就是深拷貝。 基本原理: 1.遞歸函數2.對象內的值都是簡單數據類型時 直接進行賦值3.當我們遇到數組和對象時,可以再次調用函數,利用遞歸去拷貝數組和對象內的每個值4.先數組 後對象 因為數組也是對象 下麵是一個實現深拷貝的 ...
  • 我們是袋鼠雲數棧 UED 團隊,致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。 本文作者:景明 我們以一段 C 代碼為例,來看一下代碼被編譯成二進位可執行程式之後,是如何被 CPU 執行的。 在這段代碼中,只是做了非常簡單的加法操作,將 x 和 y ...
  • 前言 前面我們簡單的瞭解了 vue 初始化時的一些大概的流程,這裡我們詳細的瞭解下具體的內容; 內容 這一塊主要圍繞init.ts中的vm.$mount進行剖析。 vm.$mount vm.$mount是全局的公共方法方法,但是這是我們要找的話就要向上查找了,代碼位於scr/platforms/we ...
  • 依賴註入模式(Dependency Injection Pattern):允許我們通過將對象的依賴關係從代碼中分離出來,從而使代碼更加模塊化和可重用。 在傳統的編程模式中,一個對象可能會直接創建或者獲取它需要的其他對象,這樣會造成對象之間的緊耦合關係,難以維護和擴展。而使用依賴註入模式,則可以將對象 ...
  • 簡介 命令模式(Command Pattern)是一種數據驅動的設計模式,也是一種行為型設計模式。這種模式的請求以命令的形式包裹在對象中,並傳給調用對象。調用對象再尋找合適的對象,並把該命令傳給相應的處理者。即把請求或操作封裝成單個對象,並使其可以被參數化和延遲執行,這種方式將命令和執行者進行了有效 ...
  • 經常會聽到開發者提起單元測試的話題,那麼今天我就帶大伙一起來看看大名鼎鼎的谷歌 C++ 測試框架 GoogleTest. ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...