Chia sẻ:
Notifications
Clear all

Mô tả cách thiết kế mạch FSM đơn giản bằng Verilog.

2 Bài viết
2 Thành viên
0 Reactions
78 Lượt xem
(@admin)
Thành Viên Moderator
Tham gia: 6 năm trước
Bài viết: 27
Topic starter  

Mô tả cách thiết kế mạch FSM đơn giản bằng Verilog.


   
Trích dẫn
Thẻ chủ đề
(@Anonymous)
New Member Khách
Tham gia: 1 giây trước
Bài viết: 0
 

1. Xác định các Yếu Tố Cần Thiết của FSM

Trước khi viết bất kỳ code Verilog nào, bạn cần xác định rõ các thành phần cơ bản của FSM:

* **Trạng Thái (States):** Tập hợp các trạng thái có thể của máy. Ví dụ: `IDLE`, `READ`, `PROCESS`, `WRITE`.
* **Đầu Vào (Inputs):** Các tín hiệu điều khiển hoặc dữ liệu mà FSM nhận. Ví dụ: `start`, `data_valid`, `reset`.
* **Đầu Ra (Outputs):** Các tín hiệu mà FSM tạo ra để điều khiển các phần khác của hệ thống. Ví dụ: `data_ready`, `enable_write`.
* **Chuyển Trạng Thái (State Transitions):** Điều kiện nào sẽ khiến FSM chuyển từ trạng thái này sang trạng thái khác. Điều này thường được biểu diễn bằng một sơ đồ trạng thái (state diagram).
* **Trạng Thái Ban Đầu (Initial State):** Trạng thái mà FSM bắt đầu khi khởi động.

**2. Sơ Đồ Trạng Thái (State Diagram) - Rất Quan Trọng!**

Một sơ đồ trạng thái giúp bạn hình dung và thiết kế logic của FSM. Nó trực quan hóa các trạng thái, các chuyển trạng thái và các điều kiện kích hoạt chuyển trạng thái.

**Ví dụ:** Giả sử chúng ta muốn thiết kế một FSM đơn giản để điều khiển việc đọc dữ liệu từ một bộ nhớ.

* **Trạng Thái:**
* `IDLE`: FSM đang chờ tín hiệu `start`.
* `READ`: FSM đang đọc dữ liệu từ bộ nhớ.
* `VALIDATE`: FSM đang kiểm tra tính hợp lệ của dữ liệu.
* **Đầu Vào:**
* `clk`: Tín hiệu clock.
* `rst`: Tín hiệu reset.
* `start`: Tín hiệu bắt đầu quá trình đọc.
* `data_valid`: Tín hiệu cho biết dữ liệu từ bộ nhớ là hợp lệ.
* `data_error`: Tín hiệu cho biết dữ liệu từ bộ nhớ bị lỗi.

* **Đầu Ra:**
* `data_ready`: Tín hiệu cho biết FSM đã đọc dữ liệu hợp lệ.

**Sơ đồ trạng thái (ví dụ):**

```
+-------+ start +-------+ data_valid +-------+
| IDLE | ----------> | READ | ---------------> | VALIDATE|
+-------+ +-------+ +-------+
^ | | ^
| | reset | |
| | | data_error
+---+--------------------------------------------+------+
```

**Giải thích sơ đồ:**

* FSM bắt đầu ở trạng thái `IDLE`.
* Khi tín hiệu `start` được kích hoạt, FSM chuyển sang trạng thái `READ`.
* Ở trạng thái `READ`, FSM chờ tín hiệu `data_valid`.
* Khi `data_valid` được kích hoạt, FSM chuyển sang trạng thái `VALIDATE`.
* Ở trạng thái `VALIDATE`, nếu không có `data_error` thì `data_ready` được bật và FSM trở về trạng thái `IDLE`. Nếu có `data_error`, FSM trở về `IDLE`.
* Khi có `reset`, FSM luôn trở về trạng thái `IDLE`.

**3. Mã Verilog**

Bây giờ, chúng ta sẽ viết mã Verilog dựa trên sơ đồ trạng thái. Cấu trúc chung của một FSM trong Verilog bao gồm hai phần chính:

* **Sequential Logic (Always @(posedge clk)):** Cập nhật trạng thái hiện tại (current state) dựa trên trạng thái tiếp theo (next state).
* **Combinational Logic (Always @(*)):** Xác định trạng thái tiếp theo và các đầu ra dựa trên trạng thái hiện tại và các đầu vào.

```verilog
module simple_fsm (
input logic clk,
input logic rst,
input logic start,
input logic data_valid,
input logic data_error,
output logic data_ready
);

// Định nghĩa các trạng thái
typedef enum logic [1:0] {IDLE, READ, VALIDATE} state_t; // Sử dụng enum cho dễ đọc
state_t current_state, next_state;

// Lưu ý: độ rộng [1:0] là tối thiểu cần thiết vì ta có 3 trạng thái (cần ít nhất 2 bits)

// Sequential Logic (Cập nhật trạng thái)
always_ff @(posedge clk or posedge rst) begin // Sử dụng always_ff cho flip-flop
if (rst) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end

// Combinational Logic (Xác định trạng thái tiếp theo và đầu ra)
always_comb begin
next_state = current_state; // Giả sử mặc định không thay đổi trạng thái
data_ready = 0; // Giả sử mặc định data_ready là 0

case (current_state)
IDLE: begin
if (start) begin
next_state = READ;
end
end

READ: begin
if (data_valid) begin
next_state = VALIDATE;
end
end

VALIDATE: begin
if (!data_error) begin
data_ready = 1;
next_state = IDLE;
end else begin
next_state = IDLE; // Về IDLE nếu có lỗi
end
end

default: begin
next_state = IDLE; // Phòng trường hợp không xác định (default)
end
endcase
end

endmodule
```

**Giải thích Code:**

* **`module simple_fsm(...)`:** Khai báo module Verilog với các đầu vào và đầu ra.
* **`typedef enum logic [1:0] {IDLE, READ, VALIDATE} state_t;`:** Định nghĩa một kiểu `enum` (enumeration) để biểu diễn các trạng thái. Điều này giúp mã dễ đọc và dễ bảo trì hơn so với việc sử dụng các số magic (ví dụ: `0`, `1`, `2`). `logic [1:0]` chỉ định rằng mỗi trạng thái sẽ được biểu diễn bằng 2 bits.
* **`state_t current_state, next_state;`:** Khai báo hai biến kiểu `state_t`: `current_state` lưu trạng thái hiện tại và `next_state` lưu trạng thái tiếp theo.
* **`always_ff @(posedge clk or posedge rst)`:** Đây là một khối tuần tự (sequential block) sử dụng `always_ff` (always flip-flop) để mô tả hành vi của một flip-flop. `posedge clk` có nghĩa là khối này sẽ được kích hoạt khi có cạnh lên của tín hiệu clock. `posedge rst` cho biết khối này cũng sẽ được kích hoạt khi có cạnh lên của tín hiệu reset.
* **`if (rst) begin ... end`:** Khi có tín hiệu reset (rst = 1), `current_state` được gán về trạng thái ban đầu `IDLE`.
* **`else begin current_state <= next_state; end`:** Ngược lại, khi không có reset, `current_state` được cập nhật bằng giá trị của `next_state`.
* **`always_comb begin ... end`:** Đây là một khối tổ hợp (combinational block) sử dụng `always_comb` (always combinational). Khối này sẽ được đánh giá lại bất cứ khi nào có bất kỳ đầu vào nào của nó thay đổi. Điều này đảm bảo rằng `next_state` và `data_ready` luôn được cập nhật chính xác dựa trên trạng thái hiện tại và các đầu vào.
* **`next_state = current_state;`:** Đầu tiên, chúng ta giả định rằng trạng thái tiếp theo sẽ giống như trạng thái hiện tại. Điều này giúp đơn giản hóa logic.
* **`data_ready = 0;`:** Tương tự, chúng ta giả định rằng `data_ready` sẽ là 0 (không sẵn sàng).
* **`case (current_state) ... endcase`:** Sử dụng một câu lệnh `case` để xác định trạng thái tiếp theo và các đầu ra dựa trên trạng thái hiện tại.
* Mỗi nhánh `case` tương ứng với một trạng thái có thể có.
* Bên trong mỗi nhánh, chúng ta kiểm tra các điều kiện đầu vào và gán giá trị cho `next_state` và `data_ready` nếu cần.
* **`default: begin next_state = IDLE; end`:** Nhánh `default` là một biện pháp phòng ngừa để đảm bảo rằng FSM sẽ luôn có một trạng thái xác định, ngay cả khi có một lỗi xảy ra.

**4. Testbench (Kiểm tra)**

Viết một testbench để mô phỏng và kiểm tra FSM của bạn. Một testbench sẽ cung cấp các tín hiệu đầu vào và kiểm tra các tín hiệu đầu ra để đảm bảo rằng FSM hoạt động như mong đợi. Đây là một ví dụ testbench đơn giản:

```verilog
module simple_fsm_tb;

logic clk, rst, start, data_valid, data_error, data_ready;

// Instance của module FSM
simple_fsm uut (
.clk(clk),
.rst(rst),
.start(start),
.data_valid(data_valid),
.data_error(data_error),
.data_ready(data_ready)
);

// Clock generation
always #5 clk = ~clk; // Tạo clock 10ns

// Test sequence
initial begin
clk = 0;
rst = 1;
start = 0;
data_valid = 0;
data_error = 0;

#10 rst = 0; // Bỏ reset
#20 start = 1; // Kích hoạt start
#30 start = 0;
#40 data_valid = 1; // Dữ liệu hợp lệ
#50 data_valid = 0;
#60 data_error = 0;
#70 $display("data_ready = %b", data_ready); // Kiểm tra data_ready
#80 $finish;
end

endmodule
```

**Giải thích Testbench:**

* **`module simple_fsm_tb;`:** Khai báo module testbench.
* **`logic clk, rst, ...;`:** Khai báo các tín hiệu để kết nối với FSM.
* **`simple_fsm uut(...)`:** Tạo một instance (uut - unit under test) của module `simple_fsm`.
* **`always #5 clk = ~clk;`:** Tạo một clock với chu kỳ 10ns.
* **`initial begin ... end`:** Khối `initial` chứa các bước kiểm tra.
* Gán giá trị ban đầu cho tất cả các tín hiệu.
* Bỏ reset sau một khoảng thời gian ngắn.
* Kích hoạt tín hiệu `start`.
* Kích hoạt tín hiệu `data_valid` sau một thời gian.
* Đặt `data_error` để mô phỏng các trường hợp khác nhau.
* Sử dụng `$display` để in giá trị của `data_ready` và xác minh rằng FSM hoạt động chính xác.
* Sử dụng `$finish` để kết thúc mô phỏng.

**5. Mô Phỏng và Gỡ Lỗi**

Sử dụng một công cụ mô phỏng Verilog (ví dụ: ModelSim, Vivado Simulator, Icarus Verilog) để mô phỏng mã Verilog của bạn và testbench. Quan sát các tín hiệu và đảm bảo rằng FSM hoạt động theo mong đợi. Nếu có bất kỳ lỗi nào, hãy gỡ lỗi mã và testbench của bạn.

**Lưu ý Quan Trọng:**

* **`non-blocking assignment (<=)` trong khối `always_ff`:** Luôn sử dụng `<=` (non-blocking assignment) bên trong khối `always_ff` để mô tả hành vi của flip-flop.
* **`blocking assignment (=)` trong khối `always_comb`:** Luôn sử dụng `=` (blocking assignment) bên trong khối `always_comb` để đảm bảo rằng các tín hiệu được cập nhật theo thứ tự chính xác.
* **Tính đầy đủ của `case`:** Đảm bảo rằng tất cả các trạng thái có thể có đều được xử lý trong câu lệnh `case`. Sử dụng `default` để xử lý các trường hợp không xác định.
* **Đồng bộ hóa:** FSM phải được đồng bộ hóa với tín hiệu clock. Tất cả các thay đổi trạng thái phải xảy ra trên cạnh của clock.
* **Reset:** Cung cấp một tín hiệu reset để đưa FSM về trạng thái ban đầu.

**Tổng Kết:**

Thiết kế FSM bằng Verilog bao gồm các bước sau:

1. Xác định các yếu tố cần thiết của FSM (trạng thái, đầu vào, đầu ra, chuyển trạng thái, trạng thái ban đầu).
2. Vẽ sơ đồ trạng thái.
3. Viết mã Verilog (khối tuần tự và khối tổ hợp).
4. Viết testbench.
5. Mô phỏng và gỡ lỗi.

Hy vọng điều này giúp bạn hiểu rõ hơn về cách thiết kế mạch FSM đơn giản bằng Verilog. Chúc bạn thành công!


   
Trả lờiTrích dẫn