• Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

  • Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

  • Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

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

[Verification] Hướng dẫn tạo testbench tự kiểm tra thiết kế bằng Verilog, System Verilog

Bài viết này hướng dẫn các bạn viết một testbench đơn giản tự động kiểm tra một thiết kế. Đồng thời qua đó các bạn có thể hiểu cơ bản về việc mô phỏng và kiểm tra thiết kế. Nội dung của bài viết này dành cho các bạn mới bắt đầu tìm hiểu cách viết một testbench Verilog.

1. Môi trường mô phỏng là gì?
Môi trường mô phỏng (simulation environment) hay môi trường kiểm tra (verification environment) của thiết kế là một tổ hợp các thành phần (bao gồm cả thiết kế) cho phép công cụ mô phỏng có thể sử dụng để thực thi tính toán các dữ liệu đầu ra của thiết kế dựa trên các dữ liệu đầu vào được môi trường cung cấp. Dữ liệu đầu ra có thể hiện dưới nhiều dạng khác nhau như một file dữ liệu, hoặc hiển thị trên ngõ ra chuẩn (màn hình).
Với cùng một thiết kế, môi trường mô phỏng được xây dựng bởi các kỹ sư khác nhau có thể khác nhau nhưng đều thực thi hai nhiệm vụ cơ bản:
  1. Cấp giá trị ngõ vào cho thiết kế hoạt động
  2. Giám sát giá trị ngõ ra của thiết kế tương ứng với những giá trị ngõ vào đã cấp để đảm bảo thiết kế hoạt động chính xác
Testbench là một thành phần của môi trường mô phỏng thực thi trực tiếp hai nhiệm vụ trên hoặc chỉ cấp giá trị ngõ vào cho thiết kế hoạt động. Thuật ngữ testbench cũng có thể dùng để hàm ý nói về một môi trường mô phỏng hoàn chỉnh.

Cấu trúc môi trường mô phỏng cơ bản gồm:
  1. DUT (Design Under Test) còn được gọi là UUT (Unit Under Test) chính là thiết kế cần mô phỏng và kiểm tra. DUT sẽ được gọi và kết nối với các thành phần kiểm tra ở bên trong môi trường mô phỏng hoặc trực tiếp trong testbench.
  2. Testbench
    1. Stimulus hoặc Driver hoặc Test Vector Generator là thành phần tạo và cung cấp dữ liệu đầu vào của DUT, lái các tín hiệu đầu vào của DUT
    2. Monitor hoặc Observer là thành phần giám sát và kiểm tra các giá trị ngõ ra của DUT
Hình 1. Cấu trúc cơ bản của môi trường mô phỏng
Xin lưu ý với các bạn, đây chỉ là cấu trúc cơ bản. Hiện nay, với sự phức tạp ngày càng tăng của các thiết kế môi trường mô phỏng cũng được xây dựng dựa trên các giao thức và phương pháp được chuẩn hóa ví dụ như OVM, VMM, UVM,... Các giao thức này phân chia môi trường thành nhiều thành phần với tên gọi khác nhau nhưng vẫn không nằm ngoài 2 mục đích là tạo dữ liệu đầu vào và giám sát dữ liệu đầu ra của thiết kế.
2. Mô tả DUT 

Phần này mô tả DUT sẽ sử dụng trong bài viết này. DUT là một bộ đếm Johnson được mô tả chi tiết ở link sau:

RTL code của bộ đếm Johnson N bit:
module johnson_counter (clk, rst_n, js_count);
//
parameter N  = 4;
//
//Interface
//
input clk;
input rst_n;
output reg [N-1:0] js_count;
wire js_msb_inv;
assign js_msb_inv = ~js_count[N-1];
always @ (posedge clk) begin
 if (~rst_n)
   js_count[N-1:0] <= 0;
 else
   js_count[N-1:0] <= {js_count[N-2:0], js_msb_inv};
end
endmodule
Bộ đếm này đã được mô phỏng trong bài viết sau:


sử dụng testbench có nội dung như sau:
module tb_jc;
parameter N = 4;
//
//Interface
//
reg clk;
reg rst_n;
wire [N-1:0] js_count;
johnson_counter #(.N(N)) dut (.clk(clk), .rst_n(rst_n), .js_count(js_count));
initial begin
  clk = 0;
forever #10 clk = !clk;
end
initial begin
  rst_n = 0;
#20
rst_n = 1;
end
endmodule 
Testbench trên đơn giản chỉ làm nhiệm vụ:
  1. Gọi thiết kế
  2. Tạo giá trị ngõ vào cho xung clock clk và tín hiệu reset rst_n (Stimulus).
Monitor khi chạy mô phỏng testbench trên là cửa sổ waveform của trình mô phỏng. Qua cửa sổ này, chúng ta quan sát bằng mắt để kiểm tra giá trị của tín hiệu ngx ra js_count theo hoạt động của xung clock và reset.
Việc kiểm tra bằng mắt như trên sẽ gây mất thời gian, phụ thuộc vào "tình trạng" người quan sát nên dễ nhầm lẫn. Nhất là với các thiết kế phức tạp thì việc làm như vậy là không đảm bảo tính đúng đắn của thiết kế. Chính vì vậy, việc xây dựng các môi trường và testbench tự kiểm tra là cần thiết.
3. Mô tả cấu trúc testbench
Một file testbench có thể gồm các thành phần cơ bản như sau:
  1. Định nghĩa chỉ dẫn mô phỏng là các khai báo về đơn vị thời gian timescale, define, include các file liên quan
  2. Tạo khối testbench bằng cặp từ khóa module/endmodule
  3. Khai báo các tham số, hằng số như các parameter
  4. Khai báo giao tiếp với DUT: các tín hiệu sẽ kết nối với DUT
  5. Gọi DUT
  6. Khai báo các tín hiệu, biến sử dụng nội bộ trong testbench
  7. Tạo Stimulus
  8. Tạo Monitor
  9. Giới hạn thời gian chạy mô phỏng của testbench
Trong đó:
  • Các thành phần bắt buộc phải có đối với testbench tối thiểu là 2, 4, 5, 7. Testbench loại này như đã nói ở mục 2 phía trên.
  • Các thành phần bắt buộc phải có đối với testbench tự động kiểm tra là 2, 4, 5, 7 và 8.
Sau đây, tôi xin mô tả chi tiết từng phần của một testbench dùng để tự động kiểm tra DUT bộ đếm Johnson theo những thành phần đã trình bày ở trên.

3.1 Định nghĩa chỉ dẫn mô phỏng

Khai báo đơn vị thời gian mô phỏng
`timescale 1ns/1ns
3.2 Tạo khối testbench
module tb_auto;
   //testbench body
endmodule
3.3 Khai báo các tham số, hằng số
parameter N = 4;
parameter END_TIME = 100;
parameter VALUE_NUM = 2*N;
Trong đó:
  • N là số bit của DUT bộ đếm Johnson
  • VALUE_NUM là số giá trị cần phải kiểm tra của DUT
  • END_TIME là thời gian giới hạn của testbench khi chạy mô phỏng tính theo số chu kỳ xung clock clk. Ở đây, sau 100 xung clock clk thì mô phỏng sẽ kết thúc.
3.4 Khai báo giao tiếp với DUT
reg clk;
reg rst_n;
wire [N-1:0] js_count;
Đây chính là các ngõ vào và ngõ ra của DUT, Trong đó:
  • Ngõ vào được khai báo kiểu biến (variable) để gán giá trị lái DUT
  • Ngõ ra khai báo kiểu net chỉ để giám sát
3.5 Gọi DUT
johnson_counter #(.N(N)) dut (.clk(clk), .rst_n(rst_n), .js_count(js_count));
Dòng code trên gọi module johnson_counter và thiết lập số bit cho bộ đếm là N = 4 bit.

3.6 Khai báo các tín hiệu, biến sử dụng nội bộ

reg [N-1:0] test_value[2*N-1:0];
integer i,j;

Trong đó:
  • test_value là một mảng dùng để lưu các giá trị cần kiểm tra, giá trị này sẽ được sử dụng để so sánh với ngõ ra DUT để biết DUT có hoạt động đúng hay không.
  • i,j là hai biến lặp dùng để xây dựng Stimulus và Monitor
3.7  Tạo Stimulus

Tạo clock với chu kỳ 20 đơn vị thời gian. Đơn vị thời gian ở đây là 1ns, được quy định bởi 3.1. Nếu không có định nghĩa trước về đơn vị thời gian thì testbench sẽ chạy theo đơn vị thời gian mặc định của trình mô phỏng:

initial begin
       clk = 0;
forever #10 clk = !clk;
end

Tạo reset tích cực trong 20ns ở hai điểm là thời điểm bắt đầu mô phỏng 0ns và sau khi chạy 200ns. Điểm thứ 2 là để kiểm tra DUT có hoạt động chính xác khi bị reset trong lúc hoạt động hay không.

initial begin
        rst_n = 0;
#20
rst_n = 1;
#200
rst_n = 0;
#20
rst_n = 1;
end

3.8 Tạo Monitor

Tạo giá trị kiểm tra ngõ ra DUT. Phương pháp tạo ở đây là sử dụng một mảng lưu lại tất cả các giá trị mong muốn được kiểm tra theo thứ tự đúng như cách mà DUT hoạt động. Lưu ý, kỹ sư mô phỏng sẽ xây dựng dựa trên mô tả kỹ thuật (specification) của DUT chứ không phải theo thiết kế DUT của người thiết kế nên cách thực hiện có thể giống và khác với với code của người thiết kế.

initial begin
  j = VALUE_NUM;
  test_value[0] = 0;
  for (i = 1; i < VALUE_NUM; i=i+1) begin
 test_value[i] = {test_value[i-1][N-2:0], ~test_value[i-1][N-1]};
  end
end

Chú ý, số lượng giá trị cần kiểm tra là VALUE_NUM sẽ được lưu theo thứ tự từ ô thứ 0 đến ô thứ VALUE_NUM-1. Tuy nhiên, biến j được gán giá trị ban đầu ngoài tầm này, j = VALUE_NUM là để sử dụng xác định thời điểm testbench bắt đầu kiểm tra sau này. Cụ thể, testbench chỉ kiểm tra bắt đầu tính từ khi DUT được reset. Thời điểm bắt đầu mô phỏng cho đến trước khi reset, giá trị DUT là không xác định nên không kiểm tra.

Tạo địa chỉ để lấy giá trị test từ mảng test_value:

always @ (posedge clk) begin
  if (~rst_n) j <= 0;
else if (j == VALUE_NUM-1) j <= 0;
else j <= j + 1;
end

So sánh và kiểm tra giá trị reset của DUT:

always @ (*) begin
  if (!rst_n && (j != VALUE_NUM) && (js_count[N-1:0] != test_value[j])) begin
   $display ("--------------- SIMULATION FAIL ---------------");
  $display ("[%t]The reset value of DUT: %b\tThe TEST value: %b", $time, js_count[N-1:0], test_value[j]);
   $stop;
   end
end

Ở đây, giá trị reset được test từ khi:
  1. Tín hiệu reset tích cực mức thấp, tương ứng rst_n=0
  2. Sau khi DUT đã reset, tương ứng, j phải khác giá trị khởi tạo ban đầu
Nếu giá trị ngõ ra DUT js_count khác giá trị kiểm tra test_value, testbench sẽ hiển thị báo sai; hiện vị trí thời gian sai; hiện giá trị DUT và giá trị kiểm tra tại vị trí phát hiện sai. Cuối cùng, dừng mô phỏng bởi $stop.

So sánh và kiểm tra ngõ ra DUT trong lúc hoạt động và không có reset:

always @ (posedge clk) begin
  if (rst_n && (js_count[N-1:0] != test_value[j])) begin
    $display ("--------------- SIMULATION FAIL ---------------");
   $display ("[%t]The DUT value: %b\tThe TEST value: %b", $time, js_count[N-1:0], test_value[j]);
    $stop;
end
end

Khối kiểm tra trên chỉ lấy mẫu kiểm tra tại các vị trí cạnh lên xung clock và báo lỗi bất cứ khi nào giá trị ngõ ra DUT khác với giá trị kiểm tra. Mô phỏng cũng dừng ngay khi phát hiện lỗi.

3.9 Giới hạn thời gian chạy mô phỏng của testbench

initial begin
  repeat (END_TIME) begin
@ (posedge clk);
  end
$display ("[%t]--------------- SIMULATION PASS ---------------", $time);
$stop;
end

Kết thúc thời gian chạy mô phỏng, sau số cạnh lên xung clock clk được quy định bởi END_TIME, testbench sẽ báo mô phỏng thành công với thông điệp "SIMULATION PASS". Điều kiện PASS ở đây không cần vì chỉ cần xuất hiện 1 lỗi thì testbench đã dừng và báo FAIL ngay lập tức.

3.10 Nội dung file testbench hoàn chỉnh
`timescale 1ns/1ns
module tb_auto;
//parameter
parameter N = 4;
parameter END_TIME = 100;
parameter VALUE_NUM = 2*N;
//
//Interface of DUT
//
reg clk;
reg rst_n;
wire [N-1:0] js_count;
//DUT instance
johnson_counter #(.N(N)) dut (.clk(clk), .rst_n(rst_n), .js_count(js_count));
//internal variables
reg [N-1:0] test_value[2*N-1:0];
integer i,j;
//Generate clock
initial begin
  clk = 0;
forever #10 clk = !clk;
end
//Generate reset
initial begin
  rst_n = 0;
#20
rst_n = 1;
#200
rst_n = 0;
#20
rst_n = 1;
end
//END SIMULATION
//After END_TIME cycles, if any error occurs, this testcase is ended and pased
initial begin
  repeat (END_TIME) begin
 @ (posedge clk);
end
$display ("[%t]--------------- SIMULATION PASS ---------------", $time);
$stop;
end
//
//Automatic TEST
//
//Generate the test value table
initial begin
  j = VALUE_NUM;
  test_value[0] = 0;
  for (i = 1; i < VALUE_NUM; i=i+1) begin
 test_value[i] = {test_value[i-1][N-2:0], ~test_value[i-1][N-1]};
  end
end
//Create the address to search the test value from the above table
always @ (posedge clk) begin
  if (~rst_n) j <= 0;
else if (j == VALUE_NUM-1) j <= 0;
else j <= j + 1;
end
//
//Compare and check
//
//Check the reset value
always @ (*) begin
  if (!rst_n && (j != VALUE_NUM) && (js_count[N-1:0] != test_value[j])) begin
 $display ("--------------- SIMULATION FAIL ---------------");
$display ("[%t]The reset value of DUT: %b\tThe TEST value: %b", $time, js_count[N-1:0], test_value[j]);
$stop;
end
end
//Check the output of the Johnson counter
always @ (posedge clk) begin
  if (rst_n && (js_count[N-1:0] != test_value[j])) begin
 $display ("--------------- SIMULATION FAIL ---------------");
$display ("[%t]The DUT value: %b\tThe TEST value: %b", $time, js_count[N-1:0], test_value[j]);
$stop;
end
end
endmodule 

4. Kết quả chạy trên Questa SIM

Việc biên dịch và chạy mô phỏng với Questa SIM các bạn hãy tham khảo hướng dẫn ở link:


Ở đây, tôi chỉ phân tích kết quả mô phỏng. Nếu mô phỏng PASS, các bạn sẽ quan sát trên cửa sổ Transcript hiện thông báo "SIMULATION PASS" và thời điểm kết thúc mô phỏng ngay trước thông điệp. Đồng thời, một dòng báo vị trị code trong testbench làm ngắt mô phỏng, chính là dòng $stop trong mục 3.9.
Hình 2. Thông điệp báo PASS trên cửa sổ transcript
Nếu giá trị reset bị sai, tác giả  đã sửa giá trị reset trong RTL code của DUT thành 1 (lưu ý phải biên dịch lại DUT), kết quả mô phỏng như sau:
Hình 3: Sai giá trị reset
Sau khi reset, nếu giá trị DUT sai trong khi hoạt động thì kết quả sẽ như sau. Lưu ý, tôi sửa RTL của DUT thành bộ đếm binary tuần tự như sau:

always @ (posedge clk) begin
 if (~rst_n)
   js_count[N-1:0] <= 0;
 else
   js_count[N-1:0] <= js_count + 1;
end 

Chuỗi giá trị sẽ là:
  1. Bộ đếm Binary 4 bit:    0000 -> 0001 -> 0010
  2. Bộ đếm Johnson 4 bit:  0000 -> 0001 -> 0011
Vì vậy, testbench sẽ báo lỗi tại vị trí ngõ ra DUT bằng 0010 và giá trị cần test là 0011.
Hình 4. Thông điệp báo lỗi bộ đếm
Việc tạo một testbench kiểm tra cho phép kiểm tra nhiều trường hợp trong thời gian mô phỏng dài mà không cần tốn công dò dạng sóng. Việc xem waveform chỉ thực hiện khi bạn mới xây dựng testbench để kiểm tra tính đúng đắn của testbench và khi phát hiện lỗi để gỡ lỗi (debug).
Trên đây chỉ là một hướng dẫn cơ bản giúp bạn nhanh chóng viết một testbench. Để một DUT được kiểm tra toàn diện và đảm bảo tính đúng đắn cao thì testbench và môi trường mô phỏng có thể cần thêm nhiều thành phần khác.

Lịch sử cập nhật:
1) 2019.11.10 - Cập nhật hình ảnh

[Questa SIM] Hướng dẫn cài đặt và chạy mô phỏng cơ bản với Questa SIM

1. Giới thiệu về Questa SIM

Questa SIM là một trình mô phỏng của hãng Mentor Graphics hỗ trợ đa nền tảng như hệ điều hành UNIX, Linux và Windows. Nếu bạn nào đã từng sử dụng ModelSim, một trình mổ phỏng khác của Mentor Graphics, thì việc làm quen với Questa SIM rất nhanh chóng và dễ dàng.

Questa SIM hỗ trợ biên dịch các thiết kế viết bằng Verilog HDL, VHDL và SystemC và hỗ trợ mô phỏng các đối tượng viết bằng Verilog HDL, VHDL, SystemC, System Verilog, PSL, UPF (Unified Power Format).

Việc chạy mô phỏng với Questa SIM có thể thực hiện bằng giao diện phần mềm (GUI - Graphical User Interface), bằng chế độ lệnh (Command-line) hoặc bằng chế độ batch.
  • GUI: Chế độ sử dụng giao diện phần mềm để thực thi mô phỏng bằng các nút, menu tùy chọn hoặc bằng cách lệnh trong cửa sổ transcript mà giao diện hỗ trợ
  • Command-line: Dùng lệnh để chạy mô phỏng bằng cửa sổ lệnh (command prompt) của hệ điều hành. Questa SIM sẽ bắt đầu thực thi với các biến được đặc tả trong file modelsim.ini, một file được trình mô phỏng tự động tạo ra, hoặc một file DO tùy chọn do người dùng tạo ra.
  • Batch: Dùng lệnh để chạy mô phỏng bằng cửa sổ lệnh của hệ điều hành với các biến được nhập trực tiếp từ ngõ vào chuẩn (bàn phím) hoặc từ file; và xuất ra ngõ ra chuẩn (màn hình) hoặc file.
Hình 1. Trình tự thực thi mô phỏng trong Questa SIM
Trình tự mô phỏng gồm 5 bước cơ bản:
  1. Thu thập các file đầu vào (Verilog, VHDL, SystemC) và ánh xạ các thư viện. Bước này tự động tạo ra file modelsim.ini như đã nói ở trên.
  2. Phân tích kiểm tra code và biên dịch
  3. Tối ưu và tạo cơ sở dữ liệu đã biên dịch
  4. Mô phỏng
  5. Gỡ lỗi (Debug)
2. Download gói cài đặt


3. Cài đặt
3.1/ Giải nén thư mục cài đặt
Sau khi giải nén, thư mục QuestaSim10.2c\Mentor Graphics Questa sim SE (64bit) 10.2c chứa các file sau:
  1. questasim-gcc-4.5.0-mingw64.zip
  2. questasim-win64-10.2c.exe
  3. regassistuvm_4.3_win.exe
  4. Thuốc cho tool
3.2/ Cài đặt Questa SIM
Chạy file questasim-win64-10.2c.exe để cài đặt Questa SIM.
Chọn Next → Agree → Next → Yes  → Yes
Đến cửa sổ sau đây thì chọn NO
Hình 2. Cài đặt Questa SIM
3.3/ Cài đặt thưc viện UVM

Đây là thư viện sử dụng cho phương pháp mô phỏng mới nhất hiện nay là UVM (Universal Verification Methodology).

Chạy file regassistuvm_4.3_win.exe để cài đặt.
Chọn Next -> chọn thư mục cài đặt và Next
Hình 3. Cài đặt thư viện UVM
3.4/ C/C++ compiler cho môi trường UVM
Giải nén file questasim-gcc-4.5.0-mingw64.zip và copy thư mục "gcc-4.5.0-mingw64" vào thư mục cài đặt QuestaSim. Đây là gcc compiler dùng để tổng hợp DPI (Direct Programming Interface) dùng cho môi trường mô phỏng UVM.
Lỗi thường gặp khi thiếu C/C++ compiler:
** Fatal: (vsim-7019) Can't locate a C/C++ compiler for 'DPI Export Compilation'.
3.5/ Tạo thuốc cho Questa SIM

Copy file MentorKG.exe và patch_dll.bat trong thư mục 
QuestaSim10.2c\Mentor Graphics Questa sim SE (64bit) 10.2c\c-r-a-c-k vào thư mục cài đặt c:\questasim64_10.2c\win64\

Chạy file patch_dll.bat sẽ tự động tạo ra file mgls.dll.

Chạy file MentorKG.exe, cửa sổ hiện ra như sau:
Hình 4. Chạy file MentorKG.exe
Chờ cho đến khi file ".txt" sau được tạo ra và hiện lên như sau:
Hình 5. File *.txt
Lưu lại file trên dưới tên với đuôi mở rộng .dat trong thư mục cài đặt c:\questasim64_10.2c\win64\ 

Bấm chuột phải lên Computer -> Properties -> Advanced system
  • Chọn Environment Variables tab -> Chọn new ở khung User variables for
  • Thêm:
    • Variable name: LM_LICENSE_FILE
    • Variable value: c:\questasim64_10.2c\win64\<tên file>.dat đã tạo ở trên
Hình 6. Thiết lập biến môi trường cho Questa SIM
Khởi động thử Questa SIM, hiện giao diện bình thường là OK.
Hình 7. Giao diện Questa SIM-64 10.2c
4. Chạy mô phỏng với giao diện (GUI) của Questa SIM

4.1 Đầu vào mô phỏng
Đầu vào mô phỏng gồm một file thiết kế (đây là bộ đếm Johnson tham khảo ở lilnk sau http://nguyenquanicd.blogspot.com/2017/08/verilog-rtl-code-mo-ta-cac-loai-bo-em.html) và một file testbench tạo giá trị ngõ vào cho việc mô phỏng thiết kế:
  1. File thiết kế: johnson_counter.v
  2. File testbench: tb_jc.v
Ở đây file testbench chỉ cần tạo xung clock clk và reset ban đầu rst_n.

File RTL code của thiết kế:
module johnson_counter (clk, rst_n, js_count);
//
parameter N  = 4;
//
//Interface
//
input clk;
input rst_n;
output reg [N-1:0] js_count;
wire js_msb_inv;
assign js_msb_inv = ~js_count[N-1];
always @ (posedge clk) begin
 if (~rst_n)
   js_count[N-1:0] <= 0;
 else
   js_count[N-1:0] <= {js_count[N-2:0], js_msb_inv};
end
endmodule

File testbench:
module tb_jc;
parameter N = 4;
//
//Interface
//
reg clk;
reg rst_n;
wire [N-1:0] js_count;
johnson_counter #(.N(N)) dut (.clk(clk), .rst_n(rst_n), .js_count(js_count));
initial begin
  clk = 0;
forever #10 clk = !clk;
end
initial begin
  rst_n = 0;
#20
rst_n = 1;
end
endmodule 
4.2 Tạo project
Chọn File->New->Project...

Đặt tên project (Project name) và chọn đường dẫn lưu project (Project location) trong cửa sổ sau:
Hình 8. Tạo project
Tạo file thiết kế (Verilog, VHDL, SystemC) hoặc file mô phỏng mới (Create New File) hoặc thêm các file đã tạo sẵn vào project (Add Existing File) trong cửa sổ sau:
Hình 9. Tạo mã nguồn mô phỏng
Ở đây, tôi chọn Add Existing File và dẫn đến 2 file johnson_counter.v và tb_jc.v đã được viết trước. Open->OK->Close
Hình 10. Chọn file
4.3 Biên dịch
Bấm chuột phải vào một trong các file và chọn Compile->compile All
Hình 11. Biên dịch các file
Chú ý, ký hiệu "?" ở cột Status biểu thị file chưa được biên dịch. Nếu biên dịch lỗi thì Status là dấu chéo màu đỏ, biên dịch tốt thì Status là dấu chỉ thị màu xanh.  Xem thông tin ở cửa sổ Transcript để thấy các thông tin cảnh báo. Ví dụ ở đây, file johnson_counter.v biên dịch thành công, file tb_jc.v biên dịch lỗi.
Hình 12. Cảnh báo trên cửa sổ Transcript
Nếu lỗi, hãy nhấp chột vào dòng cảnh báo trên cửa sổ Transcript để biết lý do và vị trí lỗi. Như ví dụ này, file tb_jc.v bị lỗi ở dòng 10 gần ký hiệu "#" và đây là lỗi cú pháp. Dòng 10 như sau:

johnson_counter dut #(.N(N)) (.clk(clk), .rst_n(rst_n), .js_count(js_count));
Tôi khai báo tên gọi module là dut đặt đằng trước việc gán giá trị tham số trong khi nó phải đặt sau như dòng code sau đây:
johnson_counter #(.N(N)) dut (.clk(clk), .rst_n(rst_n), .js_count(js_count));
Sau khi sửa, bạn hãy biên dịch lại là thành công như sau:
Hình 13. Biên dịch lại các file

4.4 Chạy mô phỏng
Sau khi biên dịch thành công, đối tượng được biện dịch sẽ xuất hiện trong thư mục work của thẻ library như sau:
Hình 14. Thẻ library
Bấm chuột phải vào đối tượng testbench tb_jc để chọn chế độ chạy mô phỏng. Ở đây, tôi chọn simulate without optimization để tạo ra các tín hiệu có thể quan sát dạng sóng.
Hình 15. Chạy mô phỏng
Sau khi thực thi, một vài cửa số khác hiện lên gồm:
  • Cửa sổ sim thể hiện cấu trúc thứ bậc của các đối tượng đang được mô phỏng. Ví dụ, tb_jc gọi thiết kế có tên dut. Ngang cấp với dut là hai khối initial. Bên trong dut có 1 khối always và một phát biểu assign.
  • Cửa số Objects thể hiện các tín hiệu, biến tương ứng có trong đối tượng bên cửa sổ sim.
  • Cửa sổ Wave sẽ là nơi để quan sát dạng sóng các tín hiệu
Lựa chọn các tín hiệu cần quan sát, bấm chuột phải và Add wave
Hình 16. Lấy tín hiệu để xem dạng sóng
Chú ý bên trên thanh công cụ có một nhóm các button để điều khiển việc chạy mô phỏng. Ở đây tôi chọn chạy mô phỏng với thời gian 500 ns. Bấm run cạnh ô 500 ns.
Hình 17. Chạy mô phỏng và xem waveform

Lịch sử cập nhật:
1) 2019.July.13 - Thêm mục 3.4 C/C++ compiler cho môi trường UVM
2) 2020.Jan.04 - Sửa link hình ảnh
3) 2020.Apr.25 - Sửa link tải QuestaSim

Thứ Sáu, 4 tháng 8, 2017

[Verilog][System Verilog] Thế nào là Verilog hoặc System Verilog khả tổng hợp?

1. Giới thiệu
Verilog là một trong hai ngôn ngữ mô tả phần cứng thông dụng và sử dụng phổ biến trong công nghiệp hiện nay, ngôn ngữ kia là VHDL, tên viết tắt của VHSIC (Very High Speed Integrated Circuits) Hardware Description Language. Tuy có nhiều sự so sánh hơn thua giữa Verilog và VHDL nhưng theo quan điểm của cá nhân tôi các bạn sử dụng thành thạo ngôn ngữ nào phục vụ trực tiếp và tốt nhất cho công việc, sở thích và đam mê của bạn thì nó là tuyệt vời nhất. Từ nền tảng đó, bạn có thể dễ dàng mở rộng thêm đến các ngôn ngữ khác khi cần thiết. "Một nghề cho chín còn hơn chín nghề" là câu nói rất đáng để suy ngẫm đối với người làm kỹ thuật.

Chọn ngôn ngữ nào để bắt đầu học? đó luôn là câu hỏi đau đầu cho những người mới bắt đầu. Tôi xin để link một bài viết hay nguyên văn ở đây để các bạn nào phân vân có thể tìm thấy cấu trả lời của mình.

http://www.bitweenie.com/listings/verilog-vs-vhdl/

và một vài ý kiến của người có kinh nghiệm lâu năm:

http://www.eetimes.com/author.asp?doc_id=1283049


Quay lại vấn đề chính, Verilog HDL khả tổng hợp (Verilog for Synthesis, Synthesizable Verilog) là một phần (tập con) của ngôn ngữ Verilog và System Verilog.

Hình 1. Mối liên quan giữa các phiên bản ngôn ngữ mô tả phần cứng
Hình 1 minh họa rõ mối liên quan giữa các phiên bản ngôn ngữ, trong đó Verilog-1995 được bao hàm trong Verilog-2001, Verilog-2001 được bao hàm trong Verilog-2005 và System Verilog bao hàm các phiên bản Verilog nói chung.

Verilog, gọi tắt cho Verilog 1995/2001/2005, đã gồm 2 phần là:
  1. Phần ngôn ngữ tổng hợp được, hay gọi là khả tổng hợp (Synthesis)
  2. Phần ngôn ngữ dành cho mô phỏng (Verification)
Tuy nhiên, Verilog bị giới hạn nhiều về mô tả mô hình hệ thống và mô hình mô phỏng nên System Verilog ra đời đem lại sự nâng cấp mạnh mẽ cho Verilog không chỉ trên phương diện mô phỏng mà còn trên phương diện mô tả phần cứng tổng hợp được. Điều này làm cho các kỹ sư thiết kế phần cứng cần phân biệt rõ giữa thành phần khả tổng hợp và thành phần chỉ có thể dùng để mô tả mô hình mô phỏng.

2. Khả tổng hợp là gì?

Verilog khả tổng hợp là khả năng của RTL (Register Transfer Level) code viết bằng Verilog được phần mềm tổng hợp (synthesis tool) hiểu và biên dịch thành "phần cứng" (netlist) hoạt động chính xác theo những hành vi, chức năng mà RTL code thể hiện. "Phần cứng" ở đây là các thành phần logic như cổng OR, AND, XOR, Flip-Flop, MUX, AOI, ... gọi chung là các cell chuẩn (standard cell) được tập hợp trong một thư viện chung gọi là thư viện tổng hợp (synthesis library) hay thư viện công nghệ (technology library) hay thư viện cell chuẩn (Standard cell library).

Hình 2. Tổng hợp RTL code
Các phần mềm mô phỏng (verification) như VCS (Synopsys), Questasim (Mentor), ModelSim (Mentor), Incisive (Cadence) ... có thể hiểu toàn bộ các thành phần quy định trong chuẩn ngôn ngữ Verilog, System Verilog quy định nhưng phần mềm tổng hợp chỉ hiểu và biên dịch được một phần ngôn ngữ nhất định và phần ngôn ngữ đó được gọi là phần có thể tổng hợp được.

3. Ví dụ về sự khác nhau giữa Verilog khả tổng hợp và Verilog mô phỏng

Ví dụ, bạn có thể mô tả một thanh ghi như sau:

module veriloghdl_1 (clk, data_in, data_out);
//
//Interface
//
input clk;
input [3:0] data_in;
output reg [3:0] data_out;
initial begin
  data_out[3:0] = 4'b0000;
end
always @ (posedge clk) begin
  data_out[3:0] <= data_in[3:0];
end
endmodule 

Với đoạn code trên, bạn mong muốn thanh ghi data_out[3:0] được khởi động giá trị ban đầu là 0. Với các phần mềm mô phỏng, bạn có thể thấy được kết quả mong muốn đúng như đoạn code trên mô tả. Hình dưới đây cho thấy data_out[3:0] sẽ bằng 4'b0000 ngay từ thời điểm bắt đầu mô phỏng cho đến cạnh lên thứ nhất của xung clock clk.

Hình 2. Mô phỏng đoạn code của module veriloghdl_1
Ở đây tôi dùng phần mềm ModelSim và testbench có nội dung đơn giản như sau để chạy mô phỏng:
module tb;
//
//Interface
//
reg clk;
reg [3:0] data_in;
wire [3:0] data_out;
veriloghdl_ex dut (clk, data_in, data_out);
initial begin
  data_in = 4'b1010;
  clk = 0;
forever #10 clk = !clk;
end
endmodule
Mục đích của testbench trên là cấp giá trị data_in[3:0] và cấp xung clock clk có chu kỳ là 20 đơn vị thời gian mặc định của trình mô phỏng.

Tuy nhiên, nếu đoạn code của module veriloghdl_1 được tổng hợp thành netlist thì phần cứng được tạo thành chỉ là thanh ghi 4 bit không có giá trị khởi tạo ban đầu là 4'b0000 vì initial là thành phần không thể tổng hợp được. Giá trị thực tế của thanh ghi data_out[3:0], tính từ khi được cấp nguồn (hoặc bắt đầu mô phỏng) đến cạnh lên thứ nhất của xung clock clk, là một giá trị không xác định. Giá trị không xác định ở đây không phải là giá trị lơ lửng không rõ mức logic mà phần cứng thực tế có thể mang giá trị logic 0 hoặc 1. Hình sau sẽ cho thấy sự khác biệt.

Hình 3. Mô phỏng module veriloghdl_1 ở mức cổng (gate level) ứng với phần cứng thực tế
Trước cạnh lên thứ nhất của xung clock clk, giá trị thanh ghi là không xác định nhưng được lái về một giá trị nào đó, ví dụ như 4'b0000 như hình minh họa. Sau cạnh lên của xung clock clk, giá trị thanh ghi data_out[3:0] sẽ lấy giá trị data_in[3:0]. Vì đây là mô phỏng mức cổng nên các bạn sẽ thấy data_out[3:0] phải mất một khoảng thời gian trễ sau cạnh xung clock trươc khi đạt giá trị ổn định.

Xin lưu ý với các bạn, hình 3 là hình tôi mô phỏng trên phần cứng FPGA để minh họa nên kết quả mô phỏng sẽ hơi khác so với khi bạn tổng hợp trên thư viện công nghệ vi mạch. Cụ thể, đoạn thời gian từ khi bắt đầu mô phỏng đến cạnh lên thứ nhất của xung clock sẽ được thể hiện hoàn toàn bằng màu đỏ, biểu thị giá trị không xác định.

Vậy để viết một thanh ghi phần cứng có thể khởi động giá trị ban đầu thì làm sao? Câu trả lời là mô tả thêm tín hiệu reset kèm giá trị khởi tạo. Ví dụ như đoạn RTL code sau dùng reset mức thấp và bất đồng bộ:

module veriloghdl_2 (clk, rst_n, data_in, data_out);
//
//Interface
//
input clk;
input rst_n;
input [3:0] data_in;
output reg [3:0] data_out;
always @ (posedge clk, negedge rst_n) begin
  if (~rst_n)
 data_out[3:0] <= 4'd0;
else
    data_out[3:0] <= data_in[3:0];
end
endmodule 
Đoạn testbench dùng để mô phỏng code trên như sau (thêm đoạn lái tín hiệu reset rst_n)

module tb;
//
//Interface
//
reg clk;
reg rst_n;
reg [3:0] data_in;
wire [3:0] data_out;
veriloghdl_ex dut (clk, rst_n, data_in, data_out);
initial begin
  data_in = 4'b1010;
  clk = 0;
forever #10 clk = !clk;
end
initial begin
  rst_n = 0;
#20
rst_n = 1;
end
endmodule 

Hình 4. Mô phỏng module veriloghdl_2 ở mức cổng (gate level) ứng với phần cứng thực tế
Kết quả khi rst_n = 0, thanh ghi được reset về giá trị 4'd0 và giữ giá trị này đến khi rst_n=1 và gặp cạnh lên xung clock mới cập nhật ngõ vào data_in[3:0].

Ví dụ trên đây là là một phần nhỏ trong sự khác biệt giữ mô tả RTL code khả tổng hợp và code chỉ dùng cho mô phỏng. Những thành phần Verilog nào là khả tổng hợp sẽ được giới thiệu đến các bạn trong các bài viết khác.

Thứ Ba, 1 tháng 8, 2017

[Verilog] RTL code mô tả các loại bộ đếm - counter

Bài viết này mô tả mạch nguyên lý và RTL code của các loại bộ đếm thông dụng như bộ đếm binary, bộ đếm Gray, bộ đếm Ring, Johnson và modulo-N.
1. Bộ đếm binary
Bộ đếm binary có các đặc điểm sau đây: 
  • Bộ đếm lên/xuống tuần tự
  • Có reset đồng bộ mức thấp
  • Có tín hiệu nạp giá trị cho bộ đếm
  • Số lượng bit của bộ đếm là N
  • Có tín hiệu báo bộ đếm đang hoạt động đếm lên hoặc đếm xuống
Loại bộ đếm này rất thông dụng trong các thiết kế số để chia tần số, đếm số xung, giám sát động rộng tín hiệu, tạo địa chỉ truy cập bộ nhớ, ... Tất nhiên, tùy vào ứng dụng thực tế bộ đếm sẽ có những tùy biến khác nhau.
Hình 1. Mạch nguyên lý bộ đếm binary
RTL code cho bộ đếm binary:
module bin_counter (clk, rst_n, load, count_up, count_down, init_value, binary_count, count_on);
parameter N = 4;
////Interface
input clk;
input rst_n;
input load;
input count_up;
input count_down;
input [N-1:0] init_value;
output reg [N-1:0] binary_count;
output wire count_on;
always @ (posedge clk) begin
  if (~rst_n) binary_count[N-1:0] <= 0;
  else if (load) binary_count[N-1:0] <= init_value[N-1:0];
  else begin
    case ({count_up, count_down})
      2'b10: binary_count[N-1:0] <= binary_count[N-1:0] + 1'b1;
      2'b01: binary_count[N-1:0] <= binary_count[N-1:0] - 1'b1;
    endcase
  end
end
assign count_on = (count_down | count_up) & ~load;
endmodule
2. Bộ đếm theo mã Gray
Mã Gray có đặc điểm là 2 giá trị liên tiếp nhau chi khác nhau một bit. Mã Gray có thể được chuyển đổi từ mã binary thông qua quy tắc sau: 
  • Bit MSB của mã Gray bằng bit MSB của mã binary
  • Bit thứ n trong mã Gray bằng bit thứ n XOR với bit thứ n+1 trong mã binary
Hình 2. Mạch nguyên lý chuyển đổi từ mã binary sang mã Gray N bit
Hình 3. Ví dụ về bảng chuyển đổi giữa mã binary và gray 4 bit
Để thực hiện được một bộ đếm mã Gray, chúng ta chỉ cần thêm mạch chuyển đổi từ mã binary sang mã Gray như hình 2 vào ngõ ra bộ đếm binary. RTL code của bộ đếm mã Gray N bit như sau:
module gray_counter (clk, rst_n, load, count_up, count_down, init_value, gray_count, count_on);
parameter N = 4;
////Interface//
input clk;
input rst_n;
input load;
input count_up;
input count_down;
input [N-1:0] init_value;
output reg [N-1:0] gray_count;
output wire count_on;
reg [N-1:0] binary_count;
always @ (posedge clk) begin
if (~rst_n) binary_count[N-1:0] <= 0;
else if (load) binary_count[N-1:0] <= init_value[N-1:0];
else begin
 case ({count_up, count_down})
 2'b10: binary_count[N-1:0] <= binary_count[N-1:0] + 1'b1;
 2'b01: binary_count[N-1:0] <= binary_count[N-1:0] - 1'b1;
 endcase
end
end
assign count_on = (count_down | count_up) & ~load;
//Binary to Gray
integer i;
always @ (*) begin
 gray_count[N-1] = binary_count[N-1];
 for (i = 0; i < N-1; i = i + 1) gray_count[i] = binary_count[i] ^ binary_count[i+1];
end 
endmodule
3. Bộ đếm vòng - Ring counter
Bộ đếm vòng hoạt động như một thanh ghi dịch. Nó sẽ dịch một giá trị đã nạp trước trong một vòng khép kín theo xung clock.
Hình 4. Bộ đếm vòng
Hình 4 là bộ đếm vòng đơn giản có một tín hiệu load để nạp giá trị ban đầu. Nếu load = 0, bộ đếm sẽ hoạt động liên tục theo xung clock.
Hình 5. Ví dụ về bộ đếm vòng 4 bit với giá trị khởi động được nạp là 0001
RTL có của bộ đếm vòng N bit như sau:
module ring_counter (clk, rst_n, load, init_value, ring_count);
parameter N = 4;
////Interface
input clk;
input rst_n;
input load;
input [N-1:0] init_value;
output reg [N-1:0] ring_count;
always @ (posedge clk) begin
if (~rst_n) ring_count[N-1:0] <= 0;
else if (load) ring_count[N-1:0] <= init_value[N-1:0];
else ring_count[N-1:0] <= {ring_count[N-2:0], ring_count[N-1]};
end
endmodule
4. Bộ đếm Johnson
Bộ đếm Johnson là một loại bộ đếm theo kiểu dịch như bộ đếm vòng trên đây. Điểm khác biệt là bộ đếm Johnson là ngõ vào của LSB (bit 0) sẽ lấy bù của ngõ ra bit MSB (bit N-1).
Hình 6. Ví dụ về bộ đếm Johnson 4 bit
Hình 7. Mạch nguyên lý bộ đếm Johnson N bit
RTL code của bộ đếm Johnson N bit:
module johnson_counter (clk, rst_n, js_count);
parameter N = 4;
////Interface
input clk;
input rst_n;
output reg [N-1:0] js_count;
wire js_msb_inv;
assign js_msb_inv = ~js_count[N-1];
always @ (posedge clk) begin
if (~rst_n) js_count[N-1:0] <= 0;
else js_count[N-1:0] <= {js_count[N-2:0], js_msb_inv};
end
endmodule
5. Bộ đếm Modulo-N
Modulo-N, viết tắt là MOD-N, là bộ đếm có N trạng thái tuần tự. Giả sử các trạng thái đó tuần tự từ 0 đến N-1 thì sau N-1, trạng thái bộ đếm sẽ quay về 0. Ví dụ, bộ đếm MOD-3 sẽ có chuỗi trạng thái như sau: 0->1->2->(quay lại về 0). Bộ đếm có số bit là n thì có thể tạo thành bộ đếm modulo tối đa là MOD-2^n. Ví dụ, bộ đếm 2 bit thì tối đa là MOD-4, 3 bit thì tối đa là MOD-8. Như vậy, để tạo bộ đếm MOD-5, MOD-6 hoặc MOD-7 thì cần 3 bit.
Bộ đếm lên MOD-N với số bit tùy chọn có các đặc điểm sau đây: 
  • MODN là số trạng thái bộ đếm, tương ứng với số N trong ký hiệu MOD-N 
  • BITN là số bit của bộ đếm 
  • en là tín hiệu cho phép bộ đếm hoạt động 
  • rst_n là reset đồng bộ tích cực mức thấp
Hình 8. Mạch nguyên lý bộ đếm lên MOD-N
RTL code của bộ đếm MOD-N:
module mod_counter (clk, rst_n, en, mod_count);
parameter BITN = 3;
parameter MODN = 5;
////Interface//
input clk;
input rst_n;
input en;
output reg [BITN-1:0] mod_count;
always @ (posedge clk) begin
if (~rst_n) mod_count[BITN-1:0] <= 0;
else if (en) mod_count[BITN-1:0] <= (mod_count[BITN-1:0] + 1'b1)%MODN;
end
endmodule

Lịch sử cập nhật:
1) 2019.11.10 - Chỉnh link hình 

Chủ Nhật, 30 tháng 7, 2017

[Verilog] Làm thế nào để mô tả mạch tuần tự đồng bộ dùng Flip-Flop bằng ngôn ngữ Verilog

1. Mạch tuần tự đồng bộ dùng Flip-Flop

Mô hình mạch tuần tự đồng bộ dùng FF được minh họa trong hình sau.
Hình 1. Mô hình mạch tuần tự dùng FF (trái) và ví dụ (phải)

Mạch tuần tự dùng FF là mạch đồng bộ theo xung clock gồm các thành phần chính như sau:
  • Mạch tổ hợp là phần mạch tạo giá trị ngõ vào cho FF, mạch này thực hiện tính toán dựa trên các giá trị ngõ vào và giá trị hồi tiếp từ ngõ ra của FF (nếu có)
  • Flip-Flop (FF) là thành phần sẽ cập nhật giá trị từ mạch tổ hợp theo cạnh tích cực của xung clock, cạnh lên hoặc cạnh xuống, và giữ giá trị này đến cạnh tích cực tiếp theo. FF có thể được reset hoặc không tùy vào yêu cầu cụ thể của từng mạch.
Mạch bên phải của hình 1 là một ví dụ về mạch tuần tự, trong đó:

  • Mạch tổ hợp gồm:
    • MUX của tín hiệu sel[2:0] thực hiện chuyển đổi giá trị binary của sel[2:0] thành giá trị mã Gray
    • MUX của tín hiệu en thực hiện đưa giá trị chuyển đổi đến count_i[2:0] nếu en = 1. Nếu en = 0, count_i[2:0] sẽ lấy giá trị từ ngõ ra thanh ghi FF
  • Thanh ghi gồm 3 FF để lưu giá trị từ mạch tổ hợp hoạt động theo cạnh lên xung clock

2. Mô tả mạch tuần tự bằng Verilog

Việc mô tả mạch tuần tự sẽ gồm 2 phần:
  • Mô tả mạch tổ hợp
  • Mô tả thanh ghi (FF)

RTL code của ví dụ trên đây như sau:


module gray_count (sel, en, reset, clock, count_o);
//Interface
input [2:0] sel;
input en;
input reset;
input clock;
output reg [2:0] count_o;
//Internal signal
wire [2:0] count_i;
reg [2:0] gray_code;
//Combinational circuit
//Binary-gray converter
always @ (sel[2:0]) begin
 case (sel[2:0])
   3'b000: gray_code[2:0] = 3'b000;
  3'b001: gray_code[2:0] = 3'b001;
  3'b010: gray_code[2:0] = 3'b011;
  3'b011: gray_code[2:0] = 3'b010;
  3'b100: gray_code[2:0] = 3'b110;
  3'b101: gray_code[2:0] = 3'b111;
  3'b110: gray_code[2:0] = 3'b101;
  3'b111: gray_code[2:0] = 3'b100;
 endcase
end
//MUX enable
assign count_i[2:0] = en? gray_code[2:0]: count_o[2:0];
//Register
always @ (posedge clock) begin
  if (reset) count_o[2:0] <= 3'b000;
 else       count_o[2:0] <= count_i[2:0];
end

endmodule

Mạch tổ hợp được mô tả bằng một khối always và một phát biểu assign. Việc mô tả một mạch tổ hợp các bạn hãy tham khảo ở bài khác trong trang này.

Việc mô tả một thanh ghi, thanh ghi là phần tử gồm một hoặc nhiều FF, cần quan tâm đến các vấn đề sau:

  • Mô tả cạnh tích cực của clock:
    • Cạnh lên (posedge)
    • Cạnh xuống (negedge)
  • Mô tả reset quan tâm đến 2 yếu tố:
    • Loại reset, đồng bộ hoặc bất đồng bộ. Reset đồng bộ là sự kiện reset phụ thuộc vào xung clock. Sự kiện reset chỉ xảy ra khi tín hiệu reset tích cực và có cạnh tích cực của xung clock. Reset bất đồng bộ là sự kiện reset xảy ra bất cứ khi nào tín hiệu reset tích cực, bất chấp xung clock.
    • Mức tích cực, mức cao hoặc mức thấp.

Hình 2. Sự khác nhau giữa reset đồng bộ và reset bất đồng bộ (mức tích cực của reset là mức thấp)
Đoạn RTL code ví dụ trên đây mô tả cạnh tích cực của clock là cạnh lên, loại reset sử dụng là đồng bộ với mức tích cực của tín hiệu reset là mức cao (mức 1).

Mô tả thanh ghi có reset đồng bộ tích cực mức thấp như sau:

always @ (posedge clock) begin
  if (~reset) count_o[2:0] <= 3'b000;
 else       count_o[2:0] <= count_i[2:0];
end

Mô tả thanh ghi có reset bất đồng bộ tích cực mức cao như sau:

always @ (posedge clock, posedge reset) begin
  if (reset) count_o[2:0] <= 3'b000;
 else       count_o[2:0] <= count_i[2:0];
end

Mô tả thanh ghi có reset bất đồng bộ tích cực mức thấp như sau:


always @ (posedge clock, negedge reset) begin
  if (~reset) count_o[2:0] <= 3'b000;
 else       count_o[2:0] <= count_i[2:0];

end

Mô tả thanh ghi không có reset như sau:


always @ (posedge clock) begin
  count_o[2:0] <= count_i[2:0];
end

Tất cả các mạch tuần tự khác đều có thể được mô tả bằng cách tách tiêng phần mạch tổ hợp và và phần thanh ghi như trên. Tuy nhiên, thông thường một phần hoặc toàn bộ phần mạch tổ hợp có thể được mô tả chung với thanh ghi như các đoạn RTL code minh họa sau đây.

Mô tả gộp chung MUX của tín hiệu en và bỏ qua tín hiệu trung gian count_i[2:0]:


module gray_count (sel, en, reset, clock, count_o);
//Interface
input [2:0] sel;
input en;
input reset;
input clock;
output reg [2:0] count_o;
//Internal signal
reg [2:0] gray_code;
//Combinational circuit
//Binary-gray converter
always @ (sel[2:0]) begin
 case (sel[2:0])
         3'b000: gray_code[2:0] = 3'b000;
  3'b001: gray_code[2:0] = 3'b001;
  3'b010: gray_code[2:0] = 3'b011;
  3'b011: gray_code[2:0] = 3'b010;
  3'b100: gray_code[2:0] = 3'b110;
  3'b101: gray_code[2:0] = 3'b111;
  3'b110: gray_code[2:0] = 3'b101;
  3'b111: gray_code[2:0] = 3'b100;
 endcase
end
//Register
always @ (posedge clock) begin
  if (reset) count_o[2:0] <= 3'b000;
  else if (en)
    count_o[2:0] <= gray_code[2:0];
  else count_o[2:0] <= count_o[2:0];
end
endmodule

Mô tả gộp chung tất cả phần mạch tổ hợp và thanh ghi, bỏ qua tín hiệu trung gian gray_code[2:0]:


module gray_count (sel, en, reset, clock, count_o);
//Interface
input [2:0] sel;
input en;
input reset;
input clock;
output reg [2:0] count_o;
//Register
always @ (posedge clock) begin
  if (reset) count_o[2:0] <= 3'b000;
 else if (en) begin
   case (sel[2:0])
            3'b000: count_o[2:0] <= 3'b000;
     3'b001: count_o[2:0] <= 3'b001;
     3'b010: count_o[2:0] <= 3'b011;
     3'b011: count_o[2:0] <= 3'b010;
     3'b100: count_o[2:0] <= 3'b110;
     3'b101: count_o[2:0] <= 3'b111;
     3'b110: count_o[2:0] <= 3'b101;
     3'b111: count_o[2:0] <= 3'b100;
    endcase
 end
 else count_o[2:0] <= count_o[2:0];
end

endmodule