ZYNQ自定義AXI匯流排IP應用——PWM實現呼吸燈效果
一、前言
在實時性要求較高的場合中,CPU軟體執行的方式顯然不能滿足需求,這時需要硬體邏輯實現部分功能。要想使自定義IP核被CPU訪問,就必須帶有匯流排介面。ZYNQ採用AXI BUS實現PS和PL之間的資料互動。本文以PWM為例設計了自定義AXI匯流排IP,來演示如何靈活運用ARM+FPGA的架構。
功能定義:在上一篇ZYNQ入門例項博文講解的系統中新增自定義IP核,其輸出驅動LED等實現呼吸燈效果。並且軟體通過配置暫存器方式對其進行使能、開啟/關閉配置以及選擇佔空比變化步長。另外,可以按鍵操作完成佔空比變化步長的增減。
平臺:米聯客 MIZ702N (ZYNQ-7020)
軟體:VIVADO+SDK 2017
注:自定義IP邏輯設計採用明德揚至簡設計法
二、PWM IP設計
PWM無非就是通過控制週期脈衝訊號的佔空比,也就是改變高電平在一段固定週期內的持續時間來達到控制目的。脈衝週期需要一個計數器來定時,佔空比由低變高和由高變低兩種模式同樣需要一個計數器來指示,因此這裡使用兩個巢狀的計數器cnt_cyc和cnt_mode。cnt_mode的加一條件除了要等待cnt_cyc計數完成,還要考慮佔空比的變化。
我們可以使用下降沿位置表示佔空比,位置越靠近週期值佔空比越高。在模式0中下降沿位置按照步長增大直至大於等於週期值,模式1中下降沿位置則按照步長遞減直到小於步長。使用兩個訊號up_stage和down_stage分別指示模式0和模式1。至於步長值,在配置有效時被更新,否則使用預設值。模組最終的輸出訊號在週期計數器小於下降沿位置為1,反之為零。設計完畢,上程式碼:
1 `timescale 1ns / 1ps 2 ////////////////////////////////////////////////////////////////////////////////// 3 // Company: 4 // Engineer: 5 // 6 // Create Date: 2020/03/01 18:14:44 7 // Design Name: 8 // Module Name: pwm 9 // Project Name: 10 // Target Devices: 11 // Tool Versions: 12 // Description: 13 // 14 // Dependencies: 15 // 16 // Revision: 17 // Revision 0.01 - File Created 18 // Additional Comments: 19 // 20 ////////////////////////////////////////////////////////////////////////////////// 21 22 23 module pwm( 24 input clk,//頻率100MHz 10ns 25 input rst_n, 26 input sw_en,//輸出使能 27 input sw_set_en,//步長設定使能 28 input [10-1:0] sw_freq_step,//步長數值 29 output reg led 30 ); 31 32 parameter FREQ_STEP = 10'd100; 33 34 parameter CNT_CYC_MAX = 50000; 35 36 function integer clogb2 (input integer bit_depth); 37 begin 38 for(clogb2=0;bit_depth>0;clogb2=clogb2+1) 39 bit_depth = bit_depth >> 1; 40 end 41 endfunction 42 43 localparam CNT_CYC_WIDTH = clogb2(CNT_CYC_MAX-1); 44 45 46 reg [CNT_CYC_WIDTH-1:0] cnt_cyc=0; 47 wire add_cnt_cyc,end_cnt_cyc; 48 reg [2-1:0] cnt_mode=0; 49 wire add_cnt_mode,end_cnt_mode; 50 wire up_stage,down_stage; 51 reg [CNT_CYC_WIDTH+1-1:0] neg_loc=0; 52 reg [10-1:0] freq_step=FREQ_STEP; 53 54 55 //週期計數器 計數50ms=50*1000ns = 50000_0ns 56 always@(posedge clk)begin 57 if(~rst_n)begin 58 cnt_cyc <= 0; 59 end 60 else if(add_cnt_cyc)begin 61 if(end_cnt_cyc) 62 cnt_cyc <= 0; 63 else 64 cnt_cyc <= cnt_cyc + 1'b1; 65 end 66 end 67 68 assign add_cnt_cyc = sw_en == 1; 69 assign end_cnt_cyc = add_cnt_cyc && cnt_cyc == CNT_CYC_MAX- 1; 70 71 //模式計數器 0-佔空比遞增 1-佔空比遞減 72 always@(posedge clk)begin 73 if(~rst_n)begin 74 cnt_mode <= 0; 75 end 76 else if(add_cnt_mode)begin 77 if(end_cnt_mode) 78 cnt_mode <= 0; 79 else 80 cnt_mode <= cnt_mode + 1'b1; 81 end 82 end 83 84 assign add_cnt_mode = end_cnt_cyc && ((up_stage && neg_loc >= CNT_CYC_MAX) || (down_stage && neg_loc == 0)); 85 assign end_cnt_mode = add_cnt_mode && cnt_mode == 2 - 1; 86 87 88 //變化步長設定 89 always@(posedge clk)begin 90 if(~rst_n)begin 91 freq_step <= FREQ_STEP; 92 end 93 else if(sw_set_en)begin 94 if(sw_freq_step >= 1 && sw_freq_step < 2000) 95 freq_step <= sw_freq_step; 96 else 97 freq_step <= FREQ_STEP; 98 end 99 end 100 101 //脈衝下降沿對應週期計數器數值 102 always@(posedge clk)begin 103 if(~rst_n)begin 104 neg_loc <= 0; 105 end 106 else if(end_cnt_cyc)begin 107 if(up_stage )begin//佔空比遞增階段 108 if(neg_loc < CNT_CYC_MAX) 109 neg_loc <= neg_loc + freq_step; 110 end 111 else if(down_stage )begin//佔空比遞減階段 112 if(neg_loc < freq_step) 113 neg_loc <= 0; 114 else 115 neg_loc <= neg_loc - freq_step; 116 end 117 end 118 119 end 120 121 assign up_stage = add_cnt_cyc && cnt_mode == 0; 122 assign down_stage = add_cnt_cyc && cnt_mode == 1; 123 124 125 //輸出 126 always@(posedge clk)begin 127 if(~rst_n)begin 128 led <= 1'b0;//高電平點亮 129 end 130 else if(add_cnt_cyc && cnt_cyc < neg_loc)begin 131 led <= 1'b1; 132 end 133 else 134 led <= 1'b0; 135 end 136 137 138 139 endmodule
VIVADO綜合、佈局佈線比較慢,且軟硬體級聯除錯費時費力,所以模擬是極其重要的。編寫一個簡單的testbench,定義update_freq_step task更新步長。這裡使用System Verilog語法有一定的好處。首先單驅動訊號可以統一定義為logic變數型別,其次等待時長能指定單位。
1 `timescale 1ns / 1ps 2 ////////////////////////////////////////////////////////////////////////////////// 3 // Company: 4 // Engineer: 5 // 6 // Create Date: 2020/03/01 20:49:25 7 // Design Name: 8 // Module Name: testbench 9 // Project Name: 10 // Target Devices: 11 // Tool Versions: 12 // Description: 13 // 14 // Dependencies: 15 // 16 // Revision: 17 // Revision 0.01 - File Created 18 // Additional Comments: 19 // 20 ////////////////////////////////////////////////////////////////////////////////// 21 22 23 module testbench(); 24 25 logic clk,rst_n; 26 logic sw_en,sw_set_en; 27 logic [10-1:0]sw_freq_step; 28 logic led; 29 30 parameter CYC = 10, 31 RST_TIM = 2; 32 33 defparam dut.CNT_CYC_MAX = 2000; 34 35 pwm#(.FREQ_STEP(100)) 36 dut( 37 .clk (clk) ,//頻率100MHz 10ns 38 .rst_n (rst_n) , 39 .sw_en (sw_en) ,//輸出使能 40 .sw_set_en (sw_set_en) ,//步長設定使能 41 .sw_freq_step (sw_freq_step) ,//步長數值 42 .led (led) 43 ); 44 45 initial begin 46 clk = 1; 47 forever begin 48 #(CYC/2.0); 49 clk=~clk; 50 end 51 end 52 53 initial begin 54 rst_n = 1; 55 #1; 56 rst_n = 0; 57 #(RST_TIM*CYC) rst_n = 1; 58 end 59 60 initial begin 61 sw_en = 0; 62 sw_set_en = 0; 63 sw_freq_step = 'd10; 64 #1; 65 #(RST_TIM*CYC); 66 #(CYC*10); 67 sw_en = 1; 68 69 #600us; 70 update_freq_step(50); 71 #600us; 72 $stop; 73 74 end 75 76 task update_freq_step([10-1:0] freq_step); 77 sw_set_en = 1; 78 sw_freq_step = freq_step; 79 #(1*CYC); 80 sw_set_en = 0; 81 endtask 82 83 endmoduletestbench.sv
設計較簡單,直接使用VIVADO模擬器觀察波形即可:
可以看到輸出訊號led的佔空比在不斷起伏變化,當更新freq_step為50後變化更為減慢。
配置前相鄰兩個neg_loc數值差與更新後分別是100和50。以上證明邏輯功能無誤。
三、硬體系統搭建
設計完PWM功能模組還沒有完,需要再包一層匯流排Wrapper才能被CPU訪問。建立AXI匯流排IP
在封裝器中編輯。
最終IP結構如圖:
具體操作不過多講述,直接以程式碼呈現:
1 `timescale 1 ns / 1 ps 2 3 module pwm_led_ip_v1_0 # 4 ( 5 // Users to add parameters here 6 parameter FREQ_STEP = 10'd100, 7 // User parameters ends 8 // Do not modify the parameters beyond this line 9 10 11 // Parameters of Axi Slave Bus Interface S00_AXI 12 parameter integer C_S00_AXI_DATA_WIDTH = 32, 13 parameter integer C_S00_AXI_ADDR_WIDTH = 4 14 ) 15 ( 16 // Users to add ports here 17 output led, 18 // User ports ends 19 // Do not modify the ports beyond this line 20 21 22 // Ports of Axi Slave Bus Interface S00_AXI 23 input wire s00_axi_aclk, 24 input wire s00_axi_aresetn, 25 input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_awaddr, 26 input wire [2 : 0] s00_axi_awprot, 27 input wire s00_axi_awvalid, 28 output wire s00_axi_awready, 29 input wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_wdata, 30 input wire [(C_S00_AXI_DATA_WIDTH/8)-1 : 0] s00_axi_wstrb, 31 input wire s00_axi_wvalid, 32 output wire s00_axi_wready, 33 output wire [1 : 0] s00_axi_bresp, 34 output wire s00_axi_bvalid, 35 input wire s00_axi_bready, 36 input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_araddr, 37 input wire [2 : 0] s00_axi_arprot, 38 input wire s00_axi_arvalid, 39 output wire s00_axi_arready, 40 output wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_rdata, 41 output wire [1 : 0] s00_axi_rresp, 42 output wire s00_axi_rvalid, 43 input wire s00_axi_rready 44 ); 45 // Instantiation of Axi Bus Interface S00_AXI 46 pwd_led_ip_v1_0_S00_AXI # ( 47 .C_S_AXI_DATA_WIDTH(C_S00_AXI_DATA_WIDTH), 48 .C_S_AXI_ADDR_WIDTH(C_S00_AXI_ADDR_WIDTH), 49 .FREQ_STEP(FREQ_STEP) 50 ) pwd_led_ip_v1_0_S00_AXI_inst ( 51 .S_AXI_ACLK(s00_axi_aclk), 52 .S_AXI_ARESETN(s00_axi_aresetn), 53 .S_AXI_AWADDR(s00_axi_awaddr), 54 .S_AXI_AWPROT(s00_axi_awprot), 55 .S_AXI_AWVALID(s00_axi_awvalid), 56 .S_AXI_AWREADY(s00_axi_awready), 57 .S_AXI_WDATA(s00_axi_wdata), 58 .S_AXI_WSTRB(s00_axi_wstrb), 59 .S_AXI_WVALID(s00_axi_wvalid), 60 .S_AXI_WREADY(s00_axi_wready), 61 .S_AXI_BRESP(s00_axi_bresp), 62 .S_AXI_BVALID(s00_axi_bvalid), 63 .S_AXI_BREADY(s00_axi_bready), 64 .S_AXI_ARADDR(s00_axi_araddr), 65 .S_AXI_ARPROT(s00_axi_arprot), 66 .S_AXI_ARVALID(s00_axi_arvalid), 67 .S_AXI_ARREADY(s00_axi_arready), 68 .S_AXI_RDATA(s00_axi_rdata), 69 .S_AXI_RRESP(s00_axi_rresp), 70 .S_AXI_RVALID(s00_axi_rvalid), 71 .S_AXI_RREADY(s00_axi_rready), 72 73 .led(led) 74 ); 75 76 // Add user logic here 77 78 // User logic ends 79 80 endmodulepwm_led_ip_v1_0.v
1 `timescale 1 ns / 1 ps 2 3 module pwm_led_ip_v1_0_S00_AXI # 4 ( 5 // Users to add parameters here 6 parameter FREQ_STEP = 10'd100, 7 // User parameters ends 8 // Do not modify the parameters beyond this line 9 10 // Width of S_AXI data bus 11 parameter integer C_S_AXI_DATA_WIDTH = 32, 12 // Width of S_AXI address bus 13 parameter integer C_S_AXI_ADDR_WIDTH = 4 14 ) 15 ( 16 // Users to add ports here 17 output led, 18 // User ports ends 19 // Do not modify the ports beyond this line 20 21 // Global Clock Signal 22 input wire S_AXI_ACLK, 23 // Global Reset Signal. This Signal is Active LOW 24 input wire S_AXI_ARESETN, 25 // Write address (issued by master, acceped by Slave) 26 input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_AWADDR, 27 // Write channel Protection type. This signal indicates the 28 // privilege and security level of the transaction, and whether 29 // the transaction is a data access or an instruction access. 30 input wire [2 : 0] S_AXI_AWPROT, 31 // Write address valid. This signal indicates that the master signaling 32 // valid write address and control information. 33 input wire S_AXI_AWVALID, 34 // Write address ready. This signal indicates that the slave is ready 35 // to accept an address and associated control signals. 36 output wire S_AXI_AWREADY, 37 // Write data (issued by master, acceped by Slave) 38 input wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_WDATA, 39 // Write strobes. This signal indicates which byte lanes hold 40 // valid data. There is one write strobe bit for each eight 41 // bits of the write data bus. 42 input wire [(C_S_AXI_DATA_WIDTH/8)-1 : 0] S_AXI_WSTRB, 43 // Write valid. This signal indicates that valid write 44 // data and strobes are available. 45 input wire S_AXI_WVALID, 46 // Write ready. This signal indicates that the slave 47 // can accept the write data. 48 output wire S_AXI_WREADY, 49 // Write response. This signal indicates the status 50 // of the write transaction. 51 output wire [1 : 0] S_AXI_BRESP, 52 // Write response valid. This signal indicates that the channel 53 // is signaling a valid write response. 54 output wire S_AXI_BVALID, 55 // Response ready. This signal indicates that the master 56 // can accept a write response. 57 input wire S_AXI_BREADY, 58 // Read address (issued by master, acceped by Slave) 59 input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_ARADDR, 60 // Protection type. This signal indicates the privilege 61 // and security level of the transaction, and whether the 62 // transaction is a data access or an instruction access. 63 input wire [2 : 0] S_AXI_ARPROT, 64 // Read address valid. This signal indicates that the channel 65 // is signaling valid read address and control information. 66 input wire S_AXI_ARVALID, 67 // Read address ready. This signal indicates that the slave is 68 // ready to accept an address and associated control signals. 69 output wire S_AXI_ARREADY, 70 // Read data (issued by slave) 71 output wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_RDATA, 72 // Read response. This signal indicates the status of the 73 // read transfer. 74 output wire [1 : 0] S_AXI_RRESP, 75 // Read valid. This signal indicates that the channel is 76 // signaling the required read data. 77 output wire S_AXI_RVALID, 78 // Read ready. This signal indicates that the master can 79 // accept the read data and response information. 80 input wire S_AXI_RREADY 81 ); 82 83 // AXI4LITE signals 84 reg [C_S_AXI_ADDR_WIDTH-1 : 0] axi_awaddr; 85 reg axi_awready; 86 reg axi_wready; 87 reg [1 : 0] axi_bresp; 88 reg axi_bvalid; 89 reg [C_S_AXI_ADDR_WIDTH-1 : 0] axi_araddr; 90 reg axi_arready; 91 reg [C_S_AXI_DATA_WIDTH-1 : 0] axi_rdata; 92 reg [1 : 0] axi_rresp; 93 reg axi_rvalid; 94 95 // Example-specific design signals 96 // local parameter for addressing 32 bit / 64 bit C_S_AXI_DATA_WIDTH 97 // ADDR_LSB is used for addressing 32/64 bit registers/memories 98 // ADDR_LSB = 2 for 32 bits (n downto 2) 99 // ADDR_LSB = 3 for 64 bits (n downto 3) 100 localparam integer ADDR_LSB = (C_S_AXI_DATA_WIDTH/32) + 1; 101 localparam integer OPT_MEM_ADDR_BITS = 1; 102 //---------------------------------------------- 103 //-- Signals for user logic register space example 104 //------------------------------------------------ 105 //-- Number of Slave Registers 4 106 reg [C_S_AXI_DATA_WIDTH-1:0] slv_reg0; 107 reg [C_S_AXI_DATA_WIDTH-1:0] slv_reg1; 108 reg [C_S_AXI_DATA_WIDTH-1:0] slv_reg2; 109 reg [C_S_AXI_DATA_WIDTH-1:0] slv_reg3; 110 wire slv_reg_rden; 111 wire slv_reg_wren; 112 reg [C_S_AXI_DATA_WIDTH-1:0] reg_data_out; 113 integer byte_index; 114 reg aw_en; 115 116 // I/O Connections assignments 117 118 assign S_AXI_AWREADY = axi_awready; 119 assign S_AXI_WREADY = axi_wready; 120 assign S_AXI_BRESP = axi_bresp; 121 assign S_AXI_BVALID = axi_bvalid; 122 assign S_AXI_ARREADY = axi_arready; 123 assign S_AXI_RDATA = axi_rdata; 124 assign S_AXI_RRESP = axi_rresp; 125 assign S_AXI_RVALID = axi_rvalid; 126 // Implement axi_awready generation 127 // axi_awready is asserted for one S_AXI_ACLK clock cycle when both 128 // S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_awready is 129 // de-asserted when reset is low. 130 131 always @( posedge S_AXI_ACLK ) 132 begin 133 if ( S_AXI_ARESETN == 1'b0 ) 134 begin 135 axi_awready <= 1'b0; 136 aw_en <= 1'b1; 137 end 138 else 139 begin 140 if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en) 141 begin 142 // slave is ready to accept write address when 143 // there is a valid write address and write data 144 // on the write address and data bus. This design 145 // expects no outstanding transactions. 146 axi_awready <= 1'b1; 147 aw_en <= 1'b0; 148 end 149 else if (S_AXI_BREADY && axi_bvalid) 150 begin 151 aw_en <= 1'b1; 152 axi_awready <= 1'b0; 153 end 154 else 155 begin 156 axi_awready <= 1'b0; 157 end 158 end 159 end 160 161 // Implement axi_awaddr latching 162 // This process is used to latch the address when both 163 // S_AXI_AWVALID and S_AXI_WVALID are valid. 164 165 always @( posedge S_AXI_ACLK ) 166 begin 167 if ( S_AXI_ARESETN == 1'b0 ) 168 begin 169 axi_awaddr <= 0; 170 end 171 else 172 begin 173 if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en) 174 begin 175 // Write Address latching 176 axi_awaddr <= S_AXI_AWADDR; 177 end 178 end 179 end 180 181 // Implement axi_wready generation 182 // axi_wready is asserted for one S_AXI_ACLK clock cycle when both 183 // S_AXI_AWVALID and S_AXI_WVALID are asserted. axi_wready is 184 // de-asserted when reset is low. 185 186 always @( posedge S_AXI_ACLK ) 187 begin 188 if ( S_AXI_ARESETN == 1'b0 ) 189 begin 190 axi_wready <= 1'b0; 191 end 192 else 193 begin 194 if (~axi_wready && S_AXI_WVALID && S_AXI_AWVALID && aw_en ) 195 begin 196 // slave is ready to accept write data when 197 // there is a valid write address and write data 198 // on the write address and data bus. This design 199 // expects no outstanding transactions. 200 axi_wready <= 1'b1; 201 end 202 else 203 begin 204 axi_wready <= 1'b0; 205 end 206 end 207 end 208 209 // Implement memory mapped register select and write logic generation 210 // The write data is accepted and written to memory mapped registers when 211 // axi_awready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted. Write strobes are used to 212 // select byte enables of slave registers while writing. 213 // These registers are cleared when reset (active low) is applied. 214 // Slave register write enable is asserted when valid address and data are available 215 // and the slave is ready to accept the write address and write data. 216 assign slv_reg_wren = axi_wready && S_AXI_WVALID && axi_awready && S_AXI_AWVALID; 217 218 always @( posedge S_AXI_ACLK ) 219 begin 220 if ( S_AXI_ARESETN == 1'b0 ) 221 begin 222 slv_reg0 <= 0; 223 slv_reg1 <= 0; 224 slv_reg2 <= 0; 225 slv_reg3 <= 0; 226 end 227 else begin 228 if (slv_reg_wren) 229 begin 230 case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] ) 231 2'h0: 232 for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) 233 if ( S_AXI_WSTRB[byte_index] == 1 ) begin 234 // Respective byte enables are asserted as per write strobes 235 // Slave register 0 236 slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; 237 end 238 2'h1: 239 for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) 240 if ( S_AXI_WSTRB[byte_index] == 1 ) begin 241 // Respective byte enables are asserted as per write strobes 242 // Slave register 1 243 slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; 244 end 245 2'h2: 246 for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) 247 if ( S_AXI_WSTRB[byte_index] == 1 ) begin 248 // Respective byte enables are asserted as per write strobes 249 // Slave register 2 250 slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; 251 end 252 2'h3: 253 for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) 254 if ( S_AXI_WSTRB[byte_index] == 1 ) begin 255 // Respective byte enables are asserted as per write strobes 256 // Slave register 3 257 slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; 258 end 259 default : begin 260 slv_reg0 <= slv_reg0; 261 slv_reg1 <= slv_reg1; 262 slv_reg2 <= slv_reg2; 263 slv_reg3 <= slv_reg3; 264 end 265 endcase 266 end 267 end 268 end 269 270 // Implement write response logic generation 271 // The write response and response valid signals are asserted by the slave 272 // when axi_wready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted. 273 // This marks the acceptance of address and indicates the status of 274 // write transaction. 275 276 always @( posedge S_AXI_ACLK ) 277 begin 278 if ( S_AXI_ARESETN == 1'b0 ) 279 begin 280 axi_bvalid <= 0; 281 axi_bresp <= 2'b0; 282 end 283 else 284 begin 285 if (axi_awready && S_AXI_AWVALID && ~axi_bvalid && axi_wready && S_AXI_WVALID) 286 begin 287 // indicates a valid write response is available 288 axi_bvalid <= 1'b1; 289 axi_bresp <= 2'b0; // 'OKAY' response 290 end // work error responses in future 291 else 292 begin 293 if (S_AXI_BREADY && axi_bvalid) 294 //check if bready is asserted while bvalid is high) 295 //(there is a possibility that bready is always asserted high) 296 begin 297 axi_bvalid <= 1'b0; 298 end 299 end 300 end 301 end 302 303 // Implement axi_arready generation 304 // axi_arready is asserted for one S_AXI_ACLK clock cycle when 305 // S_AXI_ARVALID is asserted. axi_awready is 306 // de-asserted when reset (active low) is asserted. 307 // The read address is also latched when S_AXI_ARVALID is 308 // asserted. axi_araddr is reset to zero on reset assertion. 309 310 always @( posedge S_AXI_ACLK ) 311 begin 312 if ( S_AXI_ARESETN == 1'b0 ) 313 begin 314 axi_arready <= 1'b0; 315 axi_araddr <= 32'b0; 316 end 317 else 318 begin 319 if (~axi_arready && S_AXI_ARVALID) 320 begin 321 // indicates that the slave has acceped the valid read address 322 axi_arready <= 1'b1; 323 // Read address latching 324 axi_araddr <= S_AXI_ARADDR; 325 end 326 else 327 begin 328 axi_arready <= 1'b0; 329 end 330 end 331 end 332 333 // Implement axi_arvalid generation 334 // axi_rvalid is asserted for one S_AXI_ACLK clock cycle when both 335 // S_AXI_ARVALID and axi_arready are asserted. The slave registers 336 // data are available on the axi_rdata bus at this instance. The 337 // assertion of axi_rvalid marks the validity of read data on the 338 // bus and axi_rresp indicates the status of read transaction.axi_rvalid 339 // is deasserted on reset (active low). axi_rresp and axi_rdata are 340 // cleared to zero on reset (active low). 341 always @( posedge S_AXI_ACLK ) 342 begin 343 if ( S_AXI_ARESETN == 1'b0 ) 344 begin 345 axi_rvalid <= 0; 346 axi_rresp <= 0; 347 end 348 else 349 begin 350 if (axi_arready && S_AXI_ARVALID && ~axi_rvalid) 351 begin 352 // Valid read data is available at the read data bus 353 axi_rvalid <= 1'b1; 354 axi_rresp <= 2'b0; // 'OKAY' response 355 end 356 else if (axi_rvalid && S_AXI_RREADY) 357 begin 358 // Read data is accepted by the master 359 axi_rvalid <= 1'b0; 360 end 361 end 362 end 363 364 // Implement memory mapped register select and read logic generation 365 // Slave register read enable is asserted when valid address is available 366 // and the slave is ready to accept the read address. 367 assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid; 368 always @(*) 369 begin 370 // Address decoding for reading registers 371 case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] ) 372 2'h0 : reg_data_out <= slv_reg0; 373 2'h1 : reg_data_out <= slv_reg1; 374 2'h2 : reg_data_out <= slv_reg2; 375 2'h3 : reg_data_out <= slv_reg3; 376 default : reg_data_out <= 0; 377 endcase 378 end 379 380 // Output register or memory read data 381 always @( posedge S_AXI_ACLK ) 382 begin 383 if ( S_AXI_ARESETN == 1'b0 ) 384 begin 385 axi_rdata <= 0; 386 end 387 else 388 begin 389 // When there is a valid read address (S_AXI_ARVALID) with 390 // acceptance of read address by the slave (axi_arready), 391 // output the read dada 392 if (slv_reg_rden) 393 begin 394 axi_rdata <= reg_data_out; // register read data 395 end 396 end 397 end 398 399 // Add user logic here 400 pwm#(.FREQ_STEP(FREQ_STEP)) 401 u_pwm( 402 .clk (S_AXI_ACLK), 403 .rst_n (S_AXI_ARESETN), 404 .sw_en (slv_reg0[0]), 405 .sw_set_en (slv_reg1[0]), 406 .sw_freq_step (slv_reg2[10-1:0]), 407 .led (led) 408 ); 409 // User logic ends 410 411 endmodulepwm_led_ip_v1_0_S00_AXI.v
最後重新封裝
接下來搭建硬體IP子系統。
和之前相比只是添加了pwm_led_ip_0,並連線在AXI Interconnect的另一個Master介面上。使用SystemILA抓取匯流排訊號以備後續觀察。還是同樣的操作流程:生成輸出檔案,生成HDL Wrapper,新增管腳約束檔案,綜合,實現,生成位元流並匯出硬體,啟動SDK軟體環境。
四、軟體程式設計與除錯
其實CPU控制自定義IP的方式就是讀寫資料,寫就是對指標賦值,讀就是返回指標所指向地址中的資料,分別使用Xil_Out32()和Xil_In32()實現。建立pwm_led_ip.h檔案,進行地址巨集定義並編寫配置函式。為了更好地實現軟體庫的封裝和擴充套件,建立environment.h檔案來include不同的庫以及巨集定義、全域性變數定義。
軟體程式碼如下:
1 /* 2 * main.c 3 * 4 * Created on: 2020年2月22日 5 * Author: s 6 */ 7 8 9 #include "environment.h" 10 11 void GpioHandler(void *CallbackRef); 12 int setupIntSystem(XScuGic *IntcInstancePtr,XGpio *gpioInstancePtr 13 ,u32 IntrId); 14 15 int main() 16 { 17 int Status; 18 u8 i=0; 19 u32 sys_led_out=0x1; 20 u32 data_r; 21 freq_step_value = 10; 22 23 Status = gpiops_initialize(&GpioPs,GPIOPS_DEVICE_ID); 24 if (Status != XST_SUCCESS) { 25 return XST_FAILURE; 26 } 27 28 Status = gpio_initialize(&Gpio,GPIO_DEVICE_ID); 29 if (Status != XST_SUCCESS) { 30 return XST_FAILURE; 31 } 32 33 /* 34 * Set the direction for the pin to be output and 35 * Enable the Output enable for the LED Pin. 36 */ 37 gpiops_setOutput(&GpioPs,MIO_OUT_PIN_INDEX); 38 39 for(i=0;i<LOOP_NUM;i++){ 40 gpiops_setOutput(&GpioPs,EMIO_OUT_PIN_BASE_INDEX+i); 41 } 42 43 gpio_setDirect(&Gpio, 1,GPIO_CHANNEL1); 44 45 Status = setupIntSystem(&Intc,&Gpio,INTC_GPIO_INTERRUPT_ID); 46 if (Status != XST_SUCCESS) { 47 return XST_FAILURE; 48 } 49 50 Status = pwm_led_setFreqStep(freq_step_value); 51 if (Status != XST_SUCCESS) { 52 return XST_FAILURE; 53 } 54 55 printf("Initialization finish.\n"); 56 57 while(1){ 58 59 for(i=0;i<LOOP_NUM;i++){ 60 if(int_flag == 0) 61 { 62 gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+i, 0x1); 63 usleep(200*1000); 64 gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+i, 0x0); 65 } 66 else 67 { 68 gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+LOOP_NUM-1-i, 0x1); 69 usleep(200*1000); 70 gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+LOOP_NUM-1-i, 0x0); 71 } 72 } 73 74 gpiops_outputValue(&GpioPs, MIO_OUT_PIN_INDEX, sys_led_out); 75 sys_led_out = sys_led_out == 0x0 ? 0x1 : 0x0; 76 } 77 return 0; 78 } 79 80 81 82 int setupIntSystem(XScuGic *IntcInstancePtr,XGpio *gpioInstancePtr 83 ,u32 IntrId) 84 { 85 int Result; 86 /* 87 * Initialize the interrupt controller driver so that it is ready to 88 * use. 89 */ 90 91 Result = gic_initialize(&Intc,INTC_DEVICE_ID); 92 if (Result != XST_SUCCESS) { 93 return XST_FAILURE; 94 } 95 96 XScuGic_SetPriorityTriggerType(IntcInstancePtr, IntrId, 97 0xA0, 0x3); 98 99 /* 100 * Connect the interrupt handler that will be called when an 101 * interrupt occurs for the device. 102 */ 103 Result = XScuGic_Connect(IntcInstancePtr, IntrId, 104 (Xil_ExceptionHandler)GpioHandler, gpioInstancePtr); 105 if (Result != XST_SUCCESS) { 106 return Result; 107 } 108 109 /* Enable the interrupt for the GPIO device.*/ 110 XScuGic_Enable(IntcInstancePtr, IntrId); 111 112 /* 113 * Enable the GPIO channel interrupts so that push button can be 114 * detected and enable interrupts for the GPIO device 115 */ 116 XGpio_InterruptEnable(gpioInstancePtr,GPIO_CHANNEL1); 117 XGpio_InterruptGlobalEnable(gpioInstancePtr); 118 119 /* 120 * Initialize the exception table and register the interrupt 121 * controller handler with the exception table 122 */ 123 exception_enable(&Intc); 124 125 IntrFlag = 0; 126 127 return XST_SUCCESS; 128 } 129 130 void GpioHandler(void *CallbackRef) 131 { 132 XGpio *GpioPtr = (XGpio *)CallbackRef; 133 u32 gpio_inputValue; 134 135 136 /* Clear the Interrupt */ 137 XGpio_InterruptClear(GpioPtr, GPIO_CHANNEL1); 138 printf("Input interrupt routine.\n"); 139 140 //IntrFlag = 1; 141 gpio_inputValue = gpio_readValue(GpioPtr, 1); 142 switch(gpio_inputValue) 143 { 144 case 30: 145 //printf("button up\n"); 146 freq_step_value+=10; 147 pwm_led_setFreqStep(freq_step_value); 148 break; 149 case 29: 150 printf("button center\n"); 151 break; 152 case 27: 153 //printf("button left\n"); 154 int_flag = 0; 155 break; 156 case 23: 157 //printf("button right\n"); 158 int_flag = 1; 159 break; 160 case 15: 161 //print("button down\n"); 162 freq_step_value-=10; 163 pwm_led_setFreqStep(freq_step_value); 164 break; 165 } 166 167 }main.c
1 /* 2 * environment.h 3 * 4 * Created on: 2020年3月2日 5 * Author: s 6 */ 7 8 #ifndef SRC_ENVIRONMENT_H_ 9 #define SRC_ENVIRONMENT_H_ 10 11 #include "xparameters.h" 12 #include <xil_printf.h> 13 #include "sleep.h" 14 #include "xstatus.h" 15 16 #include "gpiops.h" 17 #include "gpio.h" 18 #include "pwm_led_ip.h" 19 #include "gic.h" 20 21 XGpioPs GpioPs; /* The driver instance for GPIO Device. */ 22 XGpio Gpio; 23 XScuGic Intc; /* The Instance of the Interrupt Controller Driver */ 24 25 26 27 #define printf xil_printf /* Smalller foot-print printf */ 28 #define LOOP_NUM 4 29 30 31 u32 MIO_OUT_PIN_INDEX =7; /* LED button */ 32 u32 EMIO_OUT_PIN_BASE_INDEX = 54; 33 volatile u32 IntrFlag; /* Interrupt Handler Flag */ 34 35 #endif /* SRC_ENVIRONMENT_H_ */environment.h
1 /* 2 * pwm_led_ip.h 3 * 4 * Created on: 2020年3月2日 5 * Author: s 6 */ 7 8 #ifndef SRC_PWM_LED_IP_H_ 9 #define SRC_PWM_LED_IP_H_ 10 11 #define PWM_LED_IP_S00_AXI_SLV_REG0_OFFSET 0 12 #define PWM_LED_IP_S00_AXI_SLV_REG1_OFFSET 4 13 #define PWM_LED_IP_S00_AXI_SLV_REG2_OFFSET 8 14 #define PWM_LED_IP_S00_AXI_SLV_REG3_OFFSET 12 15 16 #define PWM_LED_IP_BASEADDR XPAR_PWM_LED_IP_0_S00_AXI_BASEADDR 17 #define FREQ_STEP_SET_VALUE 30 18 19 #define PWM_LED_IP_REG_EN (PWM_LED_IP_BASEADDR+PWM_LED_IP_S00_AXI_SLV_REG0_OFFSET) 20 #define PWM_LED_IP_REG_SET_EN (PWM_LED_IP_BASEADDR+PWM_LED_IP_S00_AXI_SLV_REG1_OFFSET) 21 #define PWM_LED_IP_REG_FREQ_STEP (PWM_LED_IP_BASEADDR+PWM_LED_IP_S00_AXI_SLV_REG2_OFFSET) 22 #define PWM_LED_IP_REG_RESERVED (PWM_LED_IP_BASEADDR+PWM_LED_IP_S00_AXI_SLV_REG3_OFFSET) 23 24 volatile u32 freq_step_value; 25 26 int pwm_led_setFreqStep(u32 value) 27 { 28 29 u32 data_r; 30 Xil_Out32(PWM_LED_IP_REG_EN,0x01); 31 Xil_Out32(PWM_LED_IP_REG_SET_EN,0x01); 32 Xil_Out32(PWM_LED_IP_REG_FREQ_STEP,value); 33 data_r = Xil_In32(PWM_LED_IP_REG_FREQ_STEP); 34 Xil_Out32(PWM_LED_IP_REG_SET_EN,0x00); 35 if(data_r == value) 36 return XST_SUCCESS; 37 else 38 return XST_FAILURE; 39 40 } 41 42 #endif /* SRC_PWM_LED_IP_H_ */pwm_led_ip.h
其他檔案與上一篇ZYNQ入門例項博文相同。Run程式後多次按下按鍵,從串列埠terminal可以看出系統初始化成功,進入按鍵中斷回撥函式。開發板上呼吸燈頻率也隨著按鍵按下在變化。
最後開啟VIVADO硬體管理器,觀察AXI匯流排波形:
按下步長值增加按鍵後,會有四次寫資料操作,正好對應pwm_led_setFreqStep function中的四次Xil_Out32呼叫。每次寫後一個時鐘週期寫響應通道BVALID拉高一個時鐘週期證明寫正確。
再來觀察用於確認寫入無誤的讀操作對應匯流排波形:
讀取資料為40,與寫入一致。到此功能定義、設計規劃、硬體邏輯設計模擬、IP封裝、子系統搭建、軟體設計、板級除錯的流程全部走完。