Chủ Nhật, 13 tháng 8, 2017

[Verilog][System Verilog] Máy trạng thái hữu hạn FSM

Bài viết này phân tích cấu trúc của máy trạng thái hữu hạn FSM (Finite State Machine), cách mô tả RTL code của FSM và các vấn đề liên quan khác trên một ví dụ cụ thể về điều khiển đèn giao thông.

1. Tổng quan về FSM

1.1 Khái niệm

Máy trạng thái hữu hạn, viết tắt là FSM, là một thành phần được sử dụng phổ biến trong thiết kế vi mạch số với ưu điểm là dễ kiểm soát quá trình hoạt động của thiết kế và dễ debug hoạt động của thiết kế.
Hình 1. Mô hình cơ bản của FSM
 FSM gồm có 3 thành phần cơ bản như sau:
  1. Mạch tạo trạng thái kế tiếp (Next state logic) là mạch tổ hợp phụ thuộc vào ngõ vào FSM và giá trị trạng thái hiện tại lấy từ bộ nhớ trạng thái (state memory)
  2. Bộ nhớ trạng thái (state memory) là phần tử lưu trạng thái hiện tại của FSM nó có thể là Flip-Flop, Latch, ... lấy ngõ vào từ mạch tạo trạng thái kế tiếp. Bộ nhớ trạng thái thường được sử dụng trong các thiết kế đồng bộ là FF hoạt động theo xung clock. Một tín hiệu reset có thể phải sử dụng để khởi động FSM đến một giá trị ban đầu. Reset không cần sử dụng đối với các FSM luôn hoạt động đúng dù giá trị ban đầu của FF là bao nhiêu.
  3. Mạch tạo ngõ ra (output logic) là mạch tổ hợp tạo giá trị ngõ ra tương ứng với trạng thái hiện tại của FSM. Mạch này lấy ngõ vào là giá trị trạng thái hiện tại và có thể tổ hợp thêm ngõ vào của FSM
1.2 Phân loại

FSM được chia làm 2 loại:
  1. FSM Moore là loại có mạch tạo ngõ ra không phụ thuộc trực tiếp vào ngõ vào FSM
  2. FSM Mealy là loại có mạch tạo ngõ ra phụ thuộc trực tiếp vào ngõ vào FSM
Hình 2. FSM Moore
Hình 3: FSM Mealy
Với FSM Mealy, mạch tạo trạng thái kế tiếp và ngõ ra có thể được thay bằng ROM trong một số FPGA. Thay vì sử dụng mạch logic để tính toán giá trị ngõ ra và trạng thái kế tiếp thì bộ ROM được sử dụng để lưu các giá trị này ứng với mỗi trạng thái hiện tại. Lúc này ngõ vào và giá trị trạng thái hiện tại được sử dụng để điều khiển và tạo địa chỉ truy cập ROM.
Hình 4. FSM sử dụng ROM để lưu giá trị ngõ ra và trạng thái kế tiếp
Các bạn có thể phân biệt đâu là FSM Moore và đâu là FSM Mealy ở các mạch trong hình sau đây? (Cấu trả lời có ở cuối bài viết nhưng các bạn hãy thử suy nghĩ và cho đáp án của riêng mình).
Hình 5.1 Phân biệt FSM Moore và FSM Mealy
1.3 So sánh FSM Moore và FSM Mealy

FSM Moore có tính an toàn cao hơn FSM Mealy vì ngõ ra FSM được tổ hợp từ giá trị FF nên nếu được kết nối đến các khối khác thì tín hiệu này có thể được sử dụng làm đường hồi tiếp trở lại tổ hợp với các mạch tạo ngõ vào cho FSM. Còn đối với FSM Mealy thì việc nay sẽ tạo ra mạch bất đồng bộ.
Hình 5.2 Sử dụng FSM Moore để tránh tạo mạch bất đồng bộ không mong muốn khi hồi tiếp ngõ ra

Độ trễ ngõ ra FSM Moore được xác định rõ ràng và giới hạn trong FSM tính từ FF đi qua mạch tổ hợp tạo ngõ ra. Trong khi ngõ ra FSM Mealy phụ thuộc cả vào ngõ vào nên độ trễ của các tầng trước nối đến ngõ vào FSM cũng được tính vào độ trễ ngõ ra FSM và có thể là đường critical path (đường có độ trễ lớn nhất).
Hình 5.3 Độ trễ ngõ ra của FSM Mealy phụ thuộc vào độ trễ ngõ vào nhưng FSM Moore thì không
Mealy có ưu điểm là số trạng thái ít hơn Moore với cùng một chức năng và ngõ ra đáp ứng ngay theo sự thay đổi của ngõ vào mà không cần chờ đến cạnh lên xung clock. Nếu dùng Mealy thì cần đặc biệt chú ý đến vấn đề hồi tiếp và độ trễ cộng dồn như đã trình bày ở trên.

2. Thiết kế FSM

2.1 Yêu cầu
Sử dụng FSM để thiết kế một bộ điều khiển đèn giao thông tại một ngã tư đường với yêu cầu:
  1. Thời gian dừng tại mỗi trạng thái đèn có thể cấu hình được trước khi biên dịch RTL code
  2. Tần số điều khiển đèn là 1 Hz, tương ứng với chu kỳ 1 s
  3. Sự chuyển trạng thái đèn được xác định như bảng sau
  4. Mỗi đường sẽ có 3 tín hiệu điều khiển đèn tương ứng với 3 màu Green/Yellow/Red
  5. Trạng thái khởi động là trạng thái đèn trên cả STREET A và STREET B đều đỏ (Red)
Hình 6. Bảng chuyển trạng thái đèn giao thông

Hình 7. Sơ đồ ngã tư với đèn giao thông
2.2 Phân tích tổng quan
Từ yêu cầu ban đầu, chúng ta sẽ có một thiết kế gồm các tín hiệu như sau:
Hình 8. Sơ đồ tín hiệu giao tiếp của bộ điều khiển đèn giao thông
Trong đó:
  1. clk là xung clock tần số 1 Hz
  2. rst_n là tín hiệu dùng để khởi động bộ điều khiển đến trạng thái ban đầu Red-Red khi nó tích cực mức thấp
  3. street_a[2:0] là tín hiệu điều khiển đèn trên đường STREET A. Thứ tự bit từ 0 đến 2 tương ứng với trạng thái Red/Yellow/Green. Tích cực mức 1.
  4. street_b[2:0] là tín hiệu điều khiển đèn trên đường STREET B. Thứ tự bit từ 0 đến 2 tương ứng với trạng thái Red/Yellow/Green. Tích cực mức 1.
Bảng giá trị ngõ ra tương ứng với các trạng thái đèn như sau:
Hình 9. Bảng giá trị ngõ ra bộ điều khiển tương ứng với trạng thái đèn
Căn cứ trên hoạt động mà yêu cầu đề ra, sơ đồ khối của bộ điều khiển đèn giao thông như sau:
  1. TIME_COUNTER: dùng để đếm xunh clock xác định thời gian duy trì trạng thái của đèn. Đây là thời gian có thể cấu hình được trước khi biên dịch như yêu cầu đặt ra. Ba tín hiệu g_end, r_end và y_end báo thời điểm kết thúc của trạng thái GREEN, RED và YELLOW. Ba tín hiệu fsm_g, fsm_r và fsm_y báo trạng thái GREEN, REG và YELLOW để bộ đếm hoạt động theo giá trị cấu hình phù hợp.
  2. FSM: là máy trạng thái sẽ tạo ngõ ra street_a, street_b và fsm_g, fsm_r, fsm_y
Hình 10. Sơ đồ khối của bộ điều khiển đèn giao thông

2.3 Phân tích khối FSM
Ứng với 6 cặp trạng thái nên chúng ta có FSM 6 trạng thái như sau:
Hình 11. FSM điều khiển đèn giao thông
Trong đó, ký hiệu A và B ứng với STREET A và STREET B. G là GREEN, R là RED và Y là YELLOW. Ví dụ, AG_BR nghĩa là trạng thái STREET A đang GREEN, STREET B đang RED.

Yêu cầu đặt ra, sau khi reset thì cả hai đèn phải RED-RED nên bạn có thể chọn AR_BR1 hoặc AR_BR2. Ở đây, trạng thái AR_BR1 được chọn làm trạng thái khởi động.

Việc chuyển trạng thái chỉ xảy ra khi tín hiệu báo độ trễ tương ứng của trạng thái đó tích cực. Ví dụ, khi đèn trên STREET A hoặc STREET B đang xanh thì g_end phải tích cực thì mới chuyển sang trạng thái tiếp theo.
Hình 12. Bảng giá trị ngõ ra FSM
Sơ đồ nguyên lý tương ứng của FSM như sau:
Hình 13. Sơ đồ nguyên lý của FSM
2.4 Phân tích khối TIME_COUNTER
Vì bài này chỉ tập trung vào việc giải thích cho FSM nên khối này chỉ được đưa ra sơ đồ nguyên lý chứ không giải thích thêm để bạn đọc có thể tự nghiên cứu. Chú ý, GREEN_TIME, YELLOW_TIME và RED_TIME là các hằng số được cấu hình trước khi biên dịch RTL code. Với tần số 1 Hz, nếu muốn thời gian dừng của đèn xanh là 30s, đèn vàng là 5s và trạng thái cả hai đèn cùng đỏ là 3s thì cần gán giá trị như sau:
  • GREEN_TIME = 29
  • YELLOW_TIME = 4
  • RED_TIME = 2
Hình 14. Sơ đồ nguyên lý khối TIME_COUNTER

2.5 Hướng dẫn mô tả RTL code cho FSM

Một FSM gồm 3 phần:
  1. Mạch tạo trạng thái kế tiếp (Next state logic)
  2. Bộ nhớ trạng thái (state memory)
  3. Mạch tạo ngõ ra (output logic)
Để mô tả một FSM hoàn chỉnh, bạn cần mô tả đầy đủ 3 phần trên và thêm mô tả mã hóa trạng thái (State code). Các bạn hãy đọc RTL code và só sánh với hình 11 và hình 13 để hiểu rõ hơn.

RTL code của khối FSM:
module fsm (/*AUTOARG*/
   // Outputs
   street_a, street_b, fsm_g, fsm_y, fsm_r,
   // Inputs
   clk, rst_n, g_end, y_end, r_end
   );
//
//interface
//
input clk;
input rst_n;
input g_end;
input y_end;
input r_end;
output reg [2:0] street_a;
output reg [2:0] street_b;
output wire fsm_g;
output wire fsm_y;
output wire fsm_r;
//
//Internal signals
//
reg [2:0] current_state, next_state;
//STATE code
localparam AG_BR  = 3'd0;
localparam AY_BR  = 3'd1;
localparam AR_BR1 = 3'd2;
localparam AR_BG  = 3'd3;
localparam AR_BY  = 3'd4;
localparam AR_BR2 = 3'd5;
//Next state logic
always @ (*) begin
  case (current_state[2:0])
    AG_BR: begin
      if (g_end) next_state[2:0] = AY_BR;
      else next_state[2:0] = current_state[2:0];
    end
    AY_BR: begin
      if (y_end) next_state[2:0] = AR_BR1;
      else next_state[2:0] = current_state[2:0];
    end
    AR_BR1: begin
      if (r_end) next_state[2:0] = AR_BG;
      else next_state[2:0] = current_state[2:0];
    end
    AR_BG: begin
      if (g_end) next_state[2:0] = AR_BY;
      else next_state[2:0] = current_state[2:0];
    end
    AR_BY: begin
      if (y_end) next_state[2:0] = AR_BR2;
      else next_state[2:0] = current_state[2:0];
    end
    AR_BR2: begin
      if (r_end) next_state[2:0] = AG_BR;
      else next_state[2:0] = current_state[2:0];
    end
    default: next_state[2:0] = current_state[2:0];
  endcase
end
//STATE MEMORY
always @ (posedge clk) begin
  if (~rst_n) current_state[2:0] <= AR_BR1;
  else current_state[2:0] <= next_state[2:0];
end
//Output logic
always @ (*) begin
  case (current_state[2:0])
    AG_BR: street_a[2:0] = 3'b100;
    AY_BR: street_a[2:0] = 3'b010;
    default: street_a[2:0] = 3'b001;
  endcase
end
//
always @ (*) begin
  case (current_state[2:0])
    AR_BG: street_b[2:0] = 3'b100;
    AR_BY: street_b[2:0] = 3'b010;
    default: street_b[2:0] = 3'b001;
  endcase
end
//
assign fsm_g = (current_state[2:0] == AG_BR) | (current_state[2:0] == AR_BG);
assign fsm_y = (current_state[2:0] == AY_BR) | (current_state[2:0] == AR_BY);
assign fsm_r = (current_state[2:0] == AR_BR1) | (current_state[2:0] == AR_BR2);
endmodule
2.6 RTL code và testbench đầy đủ

Download: https://www.mediafire.com/file/v2y6d42gw9zgu7w/rtl_code_traffic_light_controller_nguyenquanicd.zip

pass (nếu có): nguyenquanicd

3. FSM là một dạng biểu diễn của mạch tuần tự


FSM thực chất chỉ là một dạng biểu diễn của mạch tuần tự. Bất kỳ mạch tuần tự dùng FF nào cũng có thể biểu diễn được dưới dạng FSM và ngược lại. Hãy xem xét một ví dụ đơn giản như sau để thấy được sự tương quan giữa một mạch tuần tự dùng FF và FSM.

Xét một bộ đếm lên 2 bit có mạch nguyên lý như sau:
Hình 15. Mạch nguyên lý bộ đếm lên 1 đơn vị
Bộ đếm này có thể được biểu diễn dưới dạng FSM như sau:
Hình 16. Biểu diễn dạng FSM của bộ đếm lên 2 bit

Sơ đồ nguyên lý của FSM trên như sau:
Hình 17. Sơ đồ nguyên lý của FSM của bộ đếm lwn 2 bit
Chúng ta sẽ xem xét mối tương quan giữa sơ đồ nguyên lý hình 15 và hình 17thanh ghi bộ đếm inc_counter[1:0] tương ứng với thanh ghi trạng thái state[1:0].
Hình 18. So sánh tìm mối tương quan giữa biểu diễn mạch tuần tự và biểu diễn FSM
Nếu các trạng thái D0, D1, D2 và D3 được mã hóa theo thứ tự là 0, 1, 2 và 3 thì giá trị inc_counter[1:0] luôn bằng với giá trị state[1:0]. Với mạch tạo trạng thái kế tiếp, khi counter_en bằng 0 thì thanh ghi trạng thái state[1:0] giữ nguyên giá trị cũ tại tất cả các trạng thái nên có thể thay đổi vị trí của nhóm MUX phụ thuộc counter_en và MUX phụ thuộc state[1:0]. Kết quả đạt được thể hiện ở hình sau:
Hình 19. Biến đổi tương đương của sơ đồ nguyên lý FSM của bộ đếm 2 bit
Mạch nguyên lý của FSM hình 19 chỉ khác mạch nguyên lý hình 15 ở bộ MUX 4 ngõ vào. Khi counter_en bằng 1, trạng thái kế tiếp có giá trị như sau:
  • Bằng D1(1) khi state[1:0] bằng D0(0)
  • Bằng D2(2) khi state[1:0] bằng D1(1)
  • Bằng D3(3) khi state[1:0] bằng D2(2)
  • Bằng D0(0) khi state[1:0] bằng D3(3)
Giá trị trạng thái kế tiếp tăng tuần tự 1 đơn vị giống chức năng của bộ cộng. Như vậy mạch nguyên lý ở hình 19 có thể được thay thế tương đương bằng mạch nguyên lý ở hình 15.

Qua ví dụ đơn giản trên, bạn đọc có thể thấy một mạch tuần tự dùng FF có thể biểu diễn dưới dạng một FSM hoặc ngược lại.

4. Mã hóa trạng thái

Một vấn đề quan trọng trong biểu diễn FSM là mã hóa trạng thái. Mỗi trạng thái FSM cần được gán 1 giá trị khác nhau để phân biệt, giá trị này gọi là mã của trạng thái và việc gán các giá trị này gọi là mã hóa trạng thái.

Việc mã hóa trạng thái khác nhau sẽ cho kết quả tổng hợp mạch khác nhau hay netlist khác nhau. Xét lại ví dụ về bộ đếm lên ở mục 3. Nếu các trạng thái từ D0 đến D3 không được mã hóa tuần tự từ 0 đến 3 thì mạch sau khi tổng hợp RTL code với biểu diễn FSM và RTL code với biểu diễn theo hình 15 sẽ có sự khác biệt lớn dù vẫn thực hiện cùng một chức năng.

Việc mã hóa trạng thái sẽ ảnh hưởng đến sự tối ưu của mạch theo hướng khác nhau như mã hóa để tối ưu về diện tích, tối ưu về công suất hay tăng tốc độ đáp ứng của mạch.

Nhiều loại mã có thể sử dụng để mã hóa trạng thái như:
  • Binary tuần tự là gán theo thứ tự 0, 1, 2, 3, 4, ... Ưu điểm là số bit trạng thái được sử dụng là tối thiểu.
  • Gray hoặc cách mã hóa làm chỉ làm 1 bit thay đổi khi chuyển trạng thái. Ưu điểm là số bit trạng thái được sử dụng là tối thiểu và tiết kiệm năng lượng vì khi hoạt động số chuyển mạch ít hơn.
  • One-hot: mỗi trạng thái được mã hóa bằng 1 bit, tại một thời điểm chỉ có 1 bit tích cực. Ưu điểm, mã hóa kiểu này thường dùng cho mạch có tốc độ đáp ứng cao, độ trễ thấp tuy nhiên số lượng FF sử dụng lớn nên tài nguyên tổng hợp và diện tích mạch tăng và có khả năng tiêu tốn năng lượng hơn. Tốc độ đáp ứng cao có được là do mạch tổ hợp ngõ ra của FSM có trạng thái được mã hóa one-hot được tối ưu ít logic hơn.
Quay lại với ví dụ về đèn giao thông, nếu sử dụng mã hóa one-hot, số bit trạng thái tăng lên 6 bit, mỗi bit ứng với 1 trạng thái nhưng logic tạo ngõ ra thì lại giảm đáng kể như mạch nguyên lý ở hình sau.
Hình 20. Mạch tổ hợp tạo ngõ ra của khối FSM bộ điều khiển đèn giao thông khi mã hóa trạng thái bằng one-hot
Có hai cánh mô tả RTL code của FSM được mã hóa trạng thái one-hot:
  • Mô tả như cách mã hóa binary thông thường là sử dụng current_state làm case_expression cho phát biểu case mô tả mạch tạo trạng thái kế tiếp. Tên trạng thái lúc này chính là giá trị của STATE
  • Mô tả sử dụng case_expression = 1. Tên trạng thái được sử dụng làm chỉ số xác định bit quy định trạng thái.
Cách mô tả FSM one-hot dùng tên trạng thái làm giá trị của STATE:
module fsm_onehot (/*AUTOARG*/
   // Outputs
   street_a, street_b, fsm_g, fsm_y, fsm_r,
   // Inputs
   clk, rst_n, g_end, y_end, r_end
   );
//
//interface
//
input clk;
input rst_n;
input g_end;
input y_end;
input r_end;
output wire [2:0] street_a;
output wire [2:0] street_b;
output wire fsm_g;
output wire fsm_y;
output wire fsm_r;
//
//Internal signals
//
reg [5:0] current_state, next_state;
//STATE code
localparam AG_BR  = 6'b000001;
localparam AY_BR  = 6'b000010;
localparam AR_BR1 = 6'b000100;
localparam AR_BG  = 6'b001000;
localparam AR_BY  = 6'd010000;
localparam AR_BR2 = 6'd100000;
//Next state logic
always @ (*) begin
  case (current_state[5:0])
    AG_BR: begin
      if (g_end) next_state[5:0] = AY_BR;
      else next_state[5:0] = current_state[5:0];
    end
    AY_BR: begin
      if (y_end) next_state[5:0] = AR_BR1;
      else next_state[5:0] = current_state[5:0];
    end
    AR_BR1: begin
      if (r_end) next_state[5:0] = AR_BG;
      else next_state[5:0] = current_state[5:0];
    end
    AR_BG: begin
      if (g_end) next_state[5:0] = AR_BY;
      else next_state[5:0] = current_state[5:0];
    end
    AR_BY: begin
      if (y_end) next_state[5:0] = AR_BR2;
      else next_state[5:0] = current_state[5:0];
    end
    AR_BR2: begin
      if (r_end) next_state[5:0] = AG_BR;
      else next_state[5:0] = current_state[5:0];
    end
    default: next_state[5:0] = current_state[5:0];
  endcase
end
//STATE MEMORY
always @ (posedge clk) begin
  if (~rst_n) current_state[5:0] <= AR_BR1;
  else current_state[5:0] <= next_state[5:0];
end
//Output logic
assign street_a[2] = current_state[0]; //AG_BR
assign street_a[1] = current_state[1]; //AY_BR
assign street_a[0] = ~(street_a[2] | street_a[1]);
//
assign street_b[2] = current_state[3]; //AR_BG
assign street_b[1] = current_state[4]; //AR_BY
assign street_b[0] = ~(street_b[2] | street_b[1]);
//
assign fsm_g = (current_state[0] | current_state[3]);
assign fsm_y = (current_state[1] | current_state[4]);
assign fsm_r = (current_state[2] | current_state[5]);
endmodule
Cách mô tả FSM one-hot với tên trạng thái được sử dụng làm chỉ số xác định bit quy định trạng thái. Ví dụ, AG_BR dùng để xác định vị trí bit 0 trong thanh ghi trạng thái, AY_BR dùng để xác định vị trí bit 1 trong thanh ghi trạng thái, ...


module fsm_onehot (/*AUTOARG*/
   // Outputs
   street_a, street_b, fsm_g, fsm_y, fsm_r,
   // Inputs
   clk, rst_n, g_end, y_end, r_end
   );
//
//interface
//
input clk;
input rst_n;
input g_end;
input y_end;
input r_end;
output wire [2:0] street_a;
output wire [2:0] street_b;
output wire fsm_g;
output wire fsm_y;
output wire fsm_r;
//
//Internal signals
//
reg [5:0] current_state, next_state;
//Bit position of STATE code
localparam AG_BR  = 3'd0;
localparam AY_BR  = 3'd1;
localparam AR_BR1 = 3'd2;
localparam AR_BG  = 3'd3;
localparam AR_BY  = 3'd4;
localparam AR_BR2 = 3'd5;
//Next state logic
always @ (*) begin
  next_state[5:0] = 6'b000000;
  case (1)
    current_state[AG_BR]: begin
      if (g_end) next_state[AY_BR] = 1'b1;
      else next_state[AG_BR] = 1'b1;
    end
    current_state[AY_BR]: begin
      if (y_end) next_state[AR_BR1] = 1'b1;
      else next_state[AY_BR] = 1'b1;
    end
    current_state[AR_BR1]: begin
      if (r_end) next_state[AR_BG] = 1'b1;
      else next_state[AR_BR1] = 1'b1;
    end
    current_state[AR_BG]: begin
      if (g_end) next_state[AR_BY] = 1'b1;
      else next_state[AR_BG] = 1'b1;
    end
    current_state[AR_BY]: begin
      if (y_end) next_state[AR_BR2] = 1'b1;
      else next_state[AR_BY] = 1'b1;
    end
    current_state[AR_BR2]: begin
      if (r_end) next_state[AG_BR] = 1'b1;
      else next_state[AR_BR2] = 1'b1;
    end
  endcase
end
//STATE MEMORY
always @ (posedge clk) begin
  if (~rst_n) current_state[5:0] <= 6'b000100;
  else current_state[5:0] <= next_state[5:0];
end
//Output logic
assign street_a[2] = current_state[0]; //AG_BR
assign street_a[1] = current_state[1]; //AY_BR
assign street_a[0] = ~(street_a[2] | street_a[1]);
//
assign street_b[2] = current_state[3]; //AR_BG
assign street_b[1] = current_state[4]; //AR_BY
assign street_b[0] = ~(street_b[2] | street_b[1]);
//
assign fsm_g = (current_state[0] | current_state[3]);
assign fsm_y = (current_state[1] | current_state[4]);
assign fsm_r = (current_state[2] | current_state[5]);
endmodule
5. Trả lời câu hỏi hình 5.1

Muốn biết mạch nào là FSM Moore, mạch nào là FSM Mealy thì chỉ cần nhìn vào các ngõ ra.
  • Chỉ cần một ngõ ra phụ thuộc trực tiếp vào ngõ vào (không qua bất kỳ FF nào) thì đó là Mealy
  • Tất cả các ngõ ra đều được tổ hợp từ các ngõ ra của FF thì đó là Moore
Các bạn nên nhớ trong mô hình FSM chỉ có STATE MEMORY là FF còn các mạch khác đều là mạch tổ hợp.

Đáp án: A, B và D là FSM Moore   -    C là FSM Mealy


Lịch sủ cập nhật:
1) 2019.11.10 - Sửa link hình ảnh

0 bình luận:

Đăng nhận xét