SystemVerilog搭建APB_I2C IP 層次化驗證平臺

来源:https://www.cnblogs.com/moluoqishi/archive/2020/02/07/12262434.html
-Advertisement-
Play Games

一、前言 近期疫情嚴重,身為社畜的我只能在家中繼續鑽研技術了。之前寫過一篇關於搭建FIFO驗證平臺的博文,利用SV的OOP特性對FIFO進行初步驗證,但有很多不足之處,比如結構不夠規範、驗證組件類不獨立於DUT等問題。此次嘗試驗證更複雜的IP,並利用SV的更多高級特性來搭建層次化驗證平臺。 二、AP ...


一、前言

  近期疫情嚴重,身為社畜的我只能在家中繼續鑽研技術了。之前寫過一篇關於搭建FIFO驗證平臺的博文,利用SV的OOP特性對FIFO進行初步驗證,但有很多不足之處,比如結構不夠規範、驗證組件類不獨立於DUT等問題。此次嘗試驗證更複雜的IP,並利用SV的更多高級特性來搭建層次化驗證平臺。

二、APB_I2C IP概述

  實踐出真知,於是在opencores網站上下載了個APB_I2C的IP核,便著手展開驗證工作。第一步是理清楚這個IP的整體功能、引腳作用以及頂層結構。整體功能從模塊名稱便可得知是帶有APB匯流排介面的I2C_master。要瞭解引腳作用與時序,直接截取SPEC上的示意圖查看:

APB_WRITE:

 APB_READ:

 I2C_PROTOCOL:

   介面和協議這裡就不細說了,感興趣的朋友查找相關的資料。至於頂層結構這方面,最好還是交給工具方便點。無奈回家沒有帶回我的虛擬機硬碟,只能下載個WINDOW版本的EDA工具了。本文使用QuestaSim,原理圖如下:

  很容易看出該模塊頂層包含APB介面模塊APB、分別用於緩存發送和接收數據的FIFO_TX和FIFO_RX,以及I2C協議轉換模塊I2X_INTERNAL_RX_TX。master通過APB匯流排訪問該IP核內部的數據緩存區和配置寄存器,無需關註內部實現。

  除了這幾個方面,配置寄存器的訪問也非常重要。IP核必須做出正確的配置和使能才可以按照需要正常工作。配置寄存器見下表:

 三、QuestaSim常用指令

  QuestaSim工具的WINDOWS/LINUX版本很容易下載到,和Modelsim的主要區別是對SV UVM的支持性較好,這一點非常符合本文的意願。但模擬過程中一次次點擊滑鼠很麻煩,只好學習學習操作命令了,寫個腳本配合SV實現自動化模擬。以下是在官方文檔user manual和tutorial中截取的常用指令及解釋。

1 Compile the source files.
vlog gates.v and2.v cache.v memory.v proc.v set.v top.v

2 Use the vopt command to optimize the design with full visibility into all design units 

vopt +acc <design_name> -o <optimized_design_name> -debugdb

The +acc argument enables full visibility into the design for debugging purposes. The -oargument  is required for naming the optimized design object. The -debugdb argument collects combinatorial and sequential logic data into the work library.

3 Use the optimized design name to load the design with the vsim command:
vsim testcounter_opt -debugdb

4 set WildcardFilter "Variable Constant Generic Parameter SpecParam Memory
Assertion Endpoint ImmediateAssert"
With this command, you remove “CellInternal” from the default list of Wildcard filters.
This allows all signals in cells to be logged by the simulator so they will be visible in the
debug environment. 

5 Add Wave *

6 add log /*

This will provide the historic values of the events of interest plus its drivers

7 run 500

  一併給出我的do腳本文件:

 1 #quit -sim
 2 
 3 set filename testbench
 4 
 5 vlog *.v *.sv
 6 
 7 vopt -debugdb +acc work.$filename -o top_opt1
 8 vsim -debugdb top_opt1
 9 
10 #vsim -vopt -debugdb +acc work.$filename
11 
12 # change WildcardFilter variables
13 set WildcardFilter "Variable Constant Generic Parameter SpecParam Memory Assertion Cover Endpoint ScVariable ImmediateAssert VHDLFile"
14 
15 add wave /$filename/*
16 add log -r /*
17 
18 run 1000ns
sim.do

 四、搭建驗證環境

  這一節是本文的核心內容了。通用的驗證環境的結構和組件如圖:

   Stimulus將測試激勵送入待測試模塊DUT,Monitor觀察響應併發送給檢Checker。遇到複雜的設計還需要設計Reference model,進而對比實際響應與黃金參考的響應區別。並且當Monitor無法簡單直接地收集DUT響應時,還需要設計VIP來解析複雜的響應信號時序。這幾天參照工具書和網上的教程視頻,根據APB_I2C模塊的特性構思出基本的驗證環境。

  APB_I2C模塊並不複雜,所以沒必要設計reference model。若想利用Monitor組件獲取DUT響應需要解析I2C協議時序,這裡編寫個VIP來幫助它解析出有效數據,進而與Stimulus數據對比。Monitor因VIP的存在得到了很大程度上的簡化,主要的功能為將等待觸發事件發生後,將數據通過MAILBOX傳輸給Checker進行比較。

  另外,為了讓Stimulus脫離具體介面信號操作,建立Generator和Initiator類分別用於產生讀寫訪問和將讀寫訪問轉換成讀寫操作對應的具體信號邏輯。為了實現OOP特性中的“細節隱藏”,建立配置類Config來配置驗證環境,這裡主要是配置Generator發送特定場景的讀寫請求。想要測試不同的功能特性,只需改動傳入Config的參數即可。到此驗證環境包含了Generator Initiator Monitor Checker Config五個驗證組件,這裡再建立Environment類將這些組件包在一起,方便調用方法。還是上圖更直觀些(有點醜,湊活看吧)

   除了驗證環境結構,好的代碼結構也能極大提高平臺的重用性。這裡將所有類及對應的屬性方法封裝到Package components中,方便被import到testbench中。驗證過程中用到的所有變數類型、參數放置在defines.sv中。

  上代碼:

  1 package components;
  2     `include "defines.sv"
  3     
  4     apb_bus_t apb_bus;
  5     logic event_tx_i2c_vld,event_tx_vld;
  6     data_t data_tx_i2c;
  7     logic data_tx_i2c_vld;
  8     
  9     //Driver
 10     class Initiator;
 11         
 12         function void init_en();
 13             apb_bus.sel = 0;
 14             apb_bus.wdata = 0;
 15             apb_bus.addr = 0;
 16             apb_bus.write = 0;
 17             apb_bus.enable = 0;
 18         endfunction
 19         
 20         task write_oper(address_t address,data_t data_w);
 21             @(posedge apb_bus.clk);
 22             #1;
 23             apb_bus.sel = 1;
 24             apb_bus.write = 1;
 25             apb_bus.wdata = data_w;
 26             apb_bus.addr = address;
 27             #T;
 28             apb_bus.enable = 1;
 29             #T;
 30             init_en();
 31         endtask
 32         
 33         task read_oper(address_t address,output data_t data_r);
 34             @(posedge apb_bus.clk);
 35             #1;
 36             apb_bus.sel = 1;
 37             apb_bus.write = 0;
 38             apb_bus.addr = address;
 39             #T;
 40             apb_bus.enable = 1;
 41             #T;
 42             data_r = apb_bus.rdata;
 43             init_en();
 44         endtask
 45     endclass
 46 
 47     typedef class Config;
 48     //Generator
 49     class Request; 
 50         data_t data_w;
 51         data_t data_r;
 52         Initiator initiator;
 53         
 54         function new();
 55             data_w = 32'h1234_5678;//32'b0001_0010_0011_0100_0101_0110_0111_1000
 56             initiator = new();
 57             clear_req();
 58         endfunction
 59         
 60         function void clear_req();
 61             initiator.init_en();
 62         endfunction
 63         
 64         task configure_reg(data_t data_reg_config,data_t data_reg_timeout);
 65             initiator.write_oper(ADDR_REG_CONFIG,data_reg_config);
 66             #(T*10);
 67             initiator.write_oper(ADD_REG_TIMEOUT,data_reg_timeout);
 68         endtask
 69         
 70         task write_data(data_t data_w);
 71             initiator.write_oper(ADDR_TX_FIFO,data_w);
 72         endtask
 73         
 74         task read_data(output data_t data_r);
 75             initiator.read_oper(ADDR_RX_FIFO,data_r);
 76         endtask
 77         
 78         task req_run(Config req_config);
 79             if(req_config.config_type == CONFIG_WR_DATA)begin
 80                 configure_reg(data_t'({30'd10,WRI_EN}),data_t'(32'd10000));
 81                 write_data(data_w);
 82             end
 83             else if(req_config.config_type == CONFIG_RD_DATA)begin
 84                 configure_reg(data_t'({30'd10,RD_EN}),data_t'(32'd10000));
 85                 read_data(data_r);
 86             end
 87         endtask
 88     
 89     endclass:Request
 90     
 91     class Config;
 92         config_type_t config_type;
 93         
 94         function new(config_type_t config_type=CONFIG_RD_DATA);
 95             this.config_type = config_type;
 96         endfunction
 97         
 98     endclass:Config
 99     
100     class Monitor;
101 
102         mailbox #(data_t) mb_data_i2c_tx;
103         mailbox #(data_t) mb_data_tx;
104         
105         function new(mailbox mb1,mailbox mb2);
106             this.mb_data_i2c_tx = mb1;
107             this.mb_data_tx = mb2;
108         endfunction
109         
110         task store_res_tx();
111             wait(event_tx_i2c_vld);
112             #(T/2.0);
113             mb_data_i2c_tx.put(data_tx_i2c);
114             $display("store_res_tx:MAILBOX PUT:'h%h",data_tx_i2c);
115         endtask
116         
117         task store_source_tx();
118             wait(event_tx_vld);
119             #(T/2.0);
120             mb_data_tx.put(apb_bus.wdata);
121             $display("store_source_tx:MAILBOX PUT:'h%h",apb_bus.wdata);
122         endtask
123         
124         task mon_run();
125             fork 
126                 store_res_tx();
127                 store_source_tx();
128             join
129         endtask
130     
131     endclass:Monitor
132     
133     class Checker;
134         uint cmp_cnt;
135         uint err_cnt;
136         data_t data_A,data_B;
137         mailbox #(data_t) mb_data_A,mb_data_B;
138         sim_res_t check_res;
139         
140         function new(mailbox mb_A,mailbox mb_B);
141             cmp_cnt = 0;
142             err_cnt = 0;
143             this.mb_data_A = mb_A;
144             this.mb_data_B = mb_B;
145         endfunction
146         
147         task collect_res();
148             mb_data_A.get(this.data_A);
149             mb_data_B.get(this.data_B);
150             $display("MAILBOX GET:'h%h, 'h%h",this.data_A,this.data_B);
151         endtask
152         
153         function sim_res_t compare(data_t dataA,data_t dataB);
154             if(dataA == dataB)begin
155                 check_res = TRUE;
156             end
157             else begin
158                 err_cnt ++;
159                 check_res = FALSE;
160             end
161             return check_res;
162         endfunction
163         
164         task check_run();
165             sim_res_t check_res;
166             collect_res();
167             check_res = compare(data_A,data_B);
168             if(check_res == TRUE)
169                 $display("RUN PASS");
170             else
171                 $display("RUN FAIL");
172         endtask
173     
174     endclass:Checker
175     
176     class Environment;
177         mailbox #(data_t) mb[2];
178         Checker chk;
179         Request req;
180         Monitor monitor;
181         Config req_config;
182         
183         function new();
184             uint i;
185             req_config = new();
186             req = new();
187             foreach(mb[i])
188                 mb[i] = new();
189             monitor = new(mb[0],mb[1]);
190             chk = new(mb[0],mb[1]);
191         endfunction
192         
193         task env_run();
194             fork
195                 req.req_run(req_config);
196                 monitor.mon_run();
197             join
198             chk.check_run();
199         endtask
200         
201     endclass:Environment
202     
203 endpackage
components.sv
 1     parameter T = 200;
 2     parameter DATA_W = 32;
 3     
 4     parameter bit [2-1:0] WRI_EN = 2'B01,
 5                         RD_EN = 2'B10;
 6     
 7     typedef int unsigned uint;
 8     //ADDR_REG_CONFIG = 'd8,//configure register
 9     //ADD_REG_TIMEOUT = 'd12;//time before starting
10     typedef enum uint {ADDR_TX_FIFO = 'd0,ADDR_RX_FIFO = 'd4,ADDR_REG_CONFIG = 'd8,ADD_REG_TIMEOUT = 'd12} address_t;
11     typedef enum uint {TRUE,FALSE} sim_res_t;
12     typedef logic [DATA_W-1:0] data_t;
13     typedef struct {
14     logic clk;
15     logic write;
16     logic sel;
17     logic enable;
18     data_t wdata;
19     data_t rdata;
20     data_t addr;
21     logic ready;
22     logic slverr;
23     } apb_bus_t;
24     
25     typedef enum {WR,RD} gen_t;
26     typedef enum {CONFIG_WR_DATA,CONFIG_RD_DATA} config_type_t;
defines.sv
 1 `timescale 1ns/1ps
 2 
 3 module i2c_slave
 4 #(parameter DATA_WIDTH=32)
 5 (
 6 input clk,
 7 input scl,
 8 inout sda,
 9 input sda_master_en,
10 
11 output logic [DATA_WIDTH-1:0] data_r,//master --> slave
12 output logic data_r_vld,
13 input [DATA_WIDTH-1:0] data_w,
14 input data_w_vld
15 );
16 
17 logic sda_r;
18 logic sda_neg,sda_pos;
19 logic cond_end,cond_start;
20 
21 assign sda = sda_master_en ? 1'bz : 1'b0;
22 
23 always@(posedge clk)begin
24     sda_r <= sda;
25 end
26 assign sda_neg = sda_r & ~sda;
27 assign sda_pos = ~sda_r & sda;
28 
29 assign cond_start = sda_neg & scl;
30 assign cond_end = sda_pos & scl;
31 
32 integer bit_index=0;
33 
34 always
35 begin
36     data_r_vld = 0;
37     wait(cond_start);
38     $display("TRANSMISSION START");
39     @(posedge scl);
40     while(bit_index < DATA_WIDTH)begin
41         @(negedge scl);
42         if (sda_master_en)begin
43             @(posedge clk);
44             data_r = {sda,data_r[DATA_WIDTH-1 -:DATA_WIDTH-1]};
45             bit_index = bit_index+1;
46             $display("Get bit%d:%d",bit_index,sda);
47         end
48     end
49     data_r_vld = 1;
50     repeat(10)
51         @(posedge clk);
52     data_r_vld = 0;
53     $display("TRANSMISSION END");
54     bit_index = 0;
55 end
56 
57 endmodule
i2c_slave.sv
  1 `timescale 1ns/1ps
  2 
  3 import components::*;
  4 
  5 
  6 module testbench;
  7 
  8 logic pclk,presetn;
  9 logic [DATA_W-1:0] paddr,pwdata,prdata;
 10 logic pwrite,pselx,penable;
 11 logic req_tx_vld;
 12 
 13 wire pready,pslverr;
 14 wire int_rx,int_tx;
 15 wire sda_enable,scl_enable;
 16 wire scl;
 17 wire sda;
 18 
 19 wire [DATA_W-1:0] data_r;
 20 wire data_r_vld;
 21 //apb_bus_t apb_bus;
 22 
 23 assign pwrite = apb_bus.write; 
 24 assign pselx = apb_bus.sel;
 25 assign penable = apb_bus.enable;
 26 assign pwdata = apb_bus.wdata;
 27 assign paddr = apb_bus.addr;
 28 
 29 assign apb_bus.rdata = prdata;
 30 assign apb_bus.ready = pready;
 31 assign apb_bus.slverr = pslverr;
 32 assign apb_bus.clk = pclk;
 33 
 34 //logic event_tx_i2c_vld,event_tx_vld;
 35 assign event_tx_vld     = req_tx_vld == 1'b1;
 36 assign event_tx_i2c_vld = data_r_vld == 1'b1;
 37 //data_t data_tx_i2c;
 38 //logic data_tx_i2c_vld;
 39 assign data_tx_i2c = data_r;
 40 assign data_tx_i2c_vld = data_r_vld;
 41 
 42 
 43 initial begin
 44     pclk = 1;
 45     forever begin
 46         #(T/2.0) pclk = ~pclk;
 47     end
 48 end
 49 
 50 initial begin
 51     presetn = 1;
 52     #1;
 53     presetn = 0;
 54     #(T*2);
 55     presetn = 1;
 56 end
 57 
 58 
 59 assign req_tx_vld = pselx & pwrite & penable & pready & ~pslverr & (paddr == ADDR_TX_FIFO || paddr == ADDR_RX_FIFO);
 60 
 61 Environment env;
 62 Config req_config;
 63 initial begin
 64     
 65     env = new();
 66     //req_config = new(CONFIG_WR_DATA);
 67     req_config = new(CONFIG_RD_DATA);
 68     env.req_config = req_config;
 69     
 70     #1;
 71     #(T*2);
 72     
 73     env.env_run();
 74 end
 75 ///////////////////////////      
 76 i2c_slave 
 77 #(.DATA_WIDTH(DATA_W))
 78 i2c_slave_vip(
 79 .clk            (pclk),
 80 .scl            (scl),
 81 .sda            (sda),
 82 .sda_master_en  (sda_enable),
 83 .data_r            (data_r),
 84 .data_r_vld        (data_r_vld),
 85 .data_w            (),
 86 .data_w_vld        ()
 87 );
 88 
 89 
 90 i2c DUT(
 91     //APB PORTS
 92     .PCLK            (pclk),
 93     .PRESETn        (presetn),
 94     .PADDR            (paddr),
 95     .PWDATA            (pwdata),
 96     .PWRITE            (pwrite),
 97     .PSELx            (pselx),
 98     .PENABLE        (penable),
 99     . PREADY        (pready),
100     . PSLVERR        (pslverr),
101     . INT_RX        (int_rx),    
102     . INT_TX        (int_tx),
103     . PRDATA        (prdata),
104     . SDA_ENABLE    (sda_enable),
105     . SCL_ENABLE    (scl_enable),
106     .SDA            (sda),
107     .SCL            (scl)    
108 
109       );
110 
111 endmodule
testbench.sv

五、模擬分析

  當Config類對象的配置參數為CONFIG_WR_DATA時,generator發起寫請求。波形如下:

   觀察列印的Log可以看出每個SCL時鐘周期採集到一個bit,MAILBOX正確傳輸,checker對比正確,故而模擬PASS。

  驗證過程中發現該模塊有很多BUG!!這裡舉兩個例子。

1 SDA為雙向埠,但當sda_enable為0時,並沒有賦值為高阻態,即釋放信號線控制權給slave。做出如下修改並讓VIP在ACK階段拉低SDA。

 2 SCL在讀操作狀態機中沒有被toggle,因此config的配置參數為CONFIG_RD_DATA時SCL沒有翻轉。在讀操作狀態機中添加翻轉邏輯,使BR_CLK_RX_O信號在counter_receive_data == clk_t_1_4時拉高,counter_receive_data==clk_t_3_4時拉低。

   波形顯示在讀操作時SCL正常翻轉。

   該模塊的讀操作很多地方不正確還有待修改,就不一一贅述了。總的來說就是根本不能用o(╥﹏╥)o 不抱希望了,之後我還是自己寫一個吧。

六、總結

  本文利用APB_I2C模塊為例搭建了層次化驗證平臺,但還有待改善。這裡列出幾點:

1 沒有完全做到測試用例與環境分離

2 沒有構建場景層給予豐富的pattern

七、參考

1 《SystemVerilog驗證——測試平臺編寫指南》

2 《QuestaSim Tutorial》

3 《QuestaSim User Manual》

4 《apbi2c_spec》

Overview :: APB to I2C :: OpenCores https://opencores.org/projects/apbi2c

 


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

-Advertisement-
Play Games
更多相關文章
  • axios 基於http客戶端的promise,面向瀏覽器和nodejs 特色 瀏覽器端發起XMLHttpRequests請求 node端發起http請求 支持Promise API 監聽請求和返回 轉化請求和返回 取消請求 自動轉化json數據 客戶端支持抵禦 安裝 使用npm: npm inst ...
  • |這個作業屬於哪個課程|軟體工程| | | | |這個作業要求在哪裡|第一次個人編程作業| |這個作業的目標|完成漢字編程| |作業正文|見下文 | |其他參考文獻|無,但是感謝洪成龍與陳徳渠的解答 | 編程信息 時間:2020.02.06|2020.02.07 代碼行數:86行|338行 需求分析 ...
  • 前言:之前打 CTF 的時候都是零零碎碎的學習Python,沒有成體系,學得不精。趁著過年的這段時間好好地系統學習一下,加強自己的python技能。同時也做一個記錄,用來總結和反思,如果能給後學者一點幫助,那就再好不過了。 [TOC] 一、Python的下載 1. 到Python的 "官網" 上看適 ...
  • 1.CS和BS CS:Client/Server 客戶端和伺服器,這種軟體往往需要安裝。比如QQ、迅雷、播放器。 優點 : 可以減輕伺服器端壓力,將部分代碼寫到客戶端,並且界面很美觀。 缺點 : 一旦伺服器更新了,客戶端也需要更新,分散式開發比較弱。 BS:Browser/Server 瀏覽器和服務 ...
  • 今日內容 裝飾器 推導式 模塊【可選】 內容回顧 1. 函數 參數 def (a1,a2):pass def (a1,a2=None):pass 預設參數推薦用不可變類型,慎用可變類型。 def( args, kwargs):pass 註意:位置參數 關鍵字參數 面試題 函數可以做參數【知識點】。 ...
  • 本來想著直接說線程池的,不過在說線程池之前,我們必須要知道併發安全隊列;因為一般情況下線程池中的線程數量是一定的,肯定不會超過某個閾值,那麼當任務太多了的時候,我們必須把多餘的任務保存到併發安全隊列中,當線程池中的線程空閑下來了,就會到併發安全隊列中拿任務; 那麼什麼是併發安全隊列呢?其實可以簡單看 ...
  • Tensorflow機器學習入門——網路可視化TensorBoard ...
  • *Preparation 1. Black's Futures Option Model (1) Key parameter: S = F, b = 0; (2) Task: Need an option for an asset (futures price = 120), assume K = ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...