Trong các bài trước, chúng ta đã thấy việc sử dụng câu lệnh Process để viết testbench cho các mạch tổ hợp. Nhưng, trong trường hợp mạch tuần tự, chúng ta cần tín hiệu xung nhịp Clock và tín hiệu reset, do đó cần có thêm hai khối lệnh bổ sung. Vì xung nhịp được tạo ra cho toàn bộ quá trình mô phỏng, do đó nó được định nghĩa bên trong một câu lệnh Process riêng biệt. Trong khi đó, tín hiệu Reset chỉ được yêu cầu khi bắt đầu các hoạt động, do đó nó không được xác định bên trong câu lệnh Process. Phần còn lại của các quy trình/phương pháp để viết các testbench cho các mạch tuần tự giống như các testbench của các mạch tổ hợp.
> Xem thêm: Testbench #1: Kiểm tra mạch tổ hợp
> Xem thêm: Testbench #2: Kiểm tra mạch tổ hợp
a) Ví dụ thiết kế: bộ đếm mô-đun M
Bộ đếm mô-đun M đếm các giá trị từ 0 đến (M-1). Bản mô tả VHDL của nó được đưa ra trong Bảng 1‑16.
Giải thích mã VHDL trong Bảng 1‑16:
Trong dòng 9, giá trị đếm tối đa, tức là M được xác định cho bộ đếm. Tiếp đến dòng 10 định nghĩa số lượng các bit N được yêu cầu cho bộ đếm để đếm được tới giá trị tối đa (M-1).
Bộ đếm có 2 cổng đầu ra là “count” và “complete_tick”. Trong đó, count được dùng để xuất giá trị đếm hiện tại của bộ đếm; complete_tick được dùng làm cờ báo hiệu khi count đạt đến giá trị cực đại M-1.
Tín hiệu “count_reg” được định nghĩa ở dòng 22 và được gán cho đầu ra “count” ở dòng 42. Vì “count_reg” được định nghĩa là kiểu unsigned, do đó kiểu của nó phải được chuyển đổi thành “std_logic_vector” ở dòng 42 vì cổng đầu ra “count” thuộc loại “std_logic_vector”.
Trong dòng 37, bộ đếm count_next được đặt thành 0 khi đạt đến giá trị tối đa, ngược lại nó sẽ bằng count_reg tăng lên 1. Sau đó, trong chu kỳ đồng hồ tiếp theo, giá trị tăng lên của “count_reg” (được lưu trữ trong “count_next”) lại được gán cho “count_reg” thông qua dòng 29.
Dòng 40 tạo ra một cờ báo cho mỗi lần hoàn thành một vòng đếm.
Bảng 1‑16 Bộ đếm mô-đun M
-- modMCounter.vhd library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity modMCounter is generic ( M : integer := 5; -- count from 0 to M-1 N : integer := 3 -- N bits required to count upto M i.e. 2**N >= M ); port( clk, reset : in std_logic; complete_tick : out std_logic; count : out std_logic_vector(N-1 downto 0) ); end modMCounter; architecture arch of modMCounter is signal count_reg, count_next : unsigned(N-1 downto 0); begin process(clk, reset) begin if reset = '1' then count_reg <= (others=>'0'); elsif clk'event and clk='1' then count_reg <= count_next; else -- note that else block is not required count_reg <= count_reg; end if; end process; -- set count_next to 0 when maximum count is reached i.e. (M-1) -- otherwise increase the count count_next <= (others=>'0') when count_reg=(M-1) else (count_reg+1); -- Generate 'tick' on each maximum count complete_tick <= '1' when count_reg = (M-1) else '0'; count <= std_logic_vector(count_reg); -- assign value to output port end arch;
b) Mô phỏng với khoảng thời gian vô hạn
Trong phần này, chúng ta tạo một testbench sẽ không tự động dừng, tức là nếu chúng ta nhấn nút “Run all” thì mô phỏng sẽ chạy mãi mãi, do đó chúng ta cần nhấn nút “Run” như trong Hình 10.13.
Giải thích testbench
Bảng 1‑17 Testbench với khoảng thời gian vô hạn cho bộ đếm mô-đun M.
-- modMCounter_tb.vhd library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity modMCounter_tb is end modMCounter_tb; architecture arch of modMCounter_tb is constant M : integer := 10; constant N : integer := 4; constant T : time := 20 ns; signal clk, reset : std_logic; -- input signal complete_tick : std_logic; -- output signal count : std_logic_vector(N-1 downto 0); -- output begin modMCounter_unit : entity work.modMCounter generic map (M => M, N => N) port map (clk=>clk, reset=>reset, complete_tick=>complete_tick, count=>count); -- continuous clock process begin clk <= '0'; wait for T/2; clk <= '1'; wait for T/2; end process; -- reset = 1 for first clock cycle and then 0 reset <= '1', '0' after T/2; end arch;
Bảng 1‑17 là testbench cho bộ đếm mô-đun M. Ở đây tín hiệu “clk” được tạo ra trong khối Process riêng biệt, tức là các dòng 27-33. Bằng cách này, tín hiệu xung nhịp sẽ có sẵn trong suốt quá trình mô phỏng. Hơn nữa, tín hiệu Reset được đặt thành ‘1’ ngay từ đầu và sau đó được đặt thành ‘0’ trong chu kỳ đồng hồ tiếp theo (dòng 37). Nếu có thêm các tín hiệu đầu vào, thì các tín hiệu đó có thể được xác định trong câu lệnh Process riêng biệt, như đã thảo luận trong các testbench của mạch tổ hợp.
Kết quả mô phỏng được thể hiện trong Hình 1‑5, trong đó các giá trị của bộ đếm đi từ 0 đến 9 khi M được đặt bằng 10 (tức là A trong hệ thập lục phân). Hơn nữa, sử dụng nút “run” để chạy mô phỏng các mạch tuần tự (thay vì run-all), như được hiển thị trong hình.
Hình 1‑5. Kế quả mô phỏng bằng testbench trong Bảng 1‑17.
c) Mô phỏng trong thời gian hữu hạn và lưu dữ liệu
Để chạy mô phỏng trong khoảng thời gian hữu hạn, chúng ta cần cung cấp số lượng xung nhịp mà chúng ta muốn chạy mô phỏng thông qua hằng số “num_of_clocks”, như được hiển thị trong dòng 23 của Bảng 1‑18. Sau đó, tại dòng 47-52 mã VHDL được thêm vào để đóng tệp sau số lượng xung nhịp mong muốn “num_of_clocks”. Dữ liệu được lưu vào tệp như được thảo luận trong phần (1.2.1.f). Bây giờ, nếu chúng ta nhấn nút run-all, thì trình mô phỏng sẽ dừng sau “num_of_clocks” chu kỳ. Lưu ý rằng, nếu dữ liệu ở định dạng signed hoặc unsigned, thì dữ liệu đó không thể được lưu vào tệp. Chúng ta cần chuyển đổi dữ liệu sang định dạng khác, ví dụ Integer, natural hoặc std_logic_vector, v.v. trước khi lưu vào tệp, như được hiển thị trong Dòng 73. Các dạng sóng mô phỏng và kết quả được lưu lần lượt được hiển thị trong Hình 1‑6 và Hình 1‑7.
Bảng 1‑18. Testbench với thời gian mô phỏng hữu hạn cho bộ đếm mô-đun M
-- modMCounter_tb2.vhd library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use std.textio.all; use ieee.std_logic_textio.all; -- require for std_logic etc. entity modMCounter_tb2 is end modMCounter_tb2; architecture arch of modMCounter_tb2 is constant M : integer := 3; -- count upto 2 (i.e. 0 to 2) constant N : integer := 4; constant T : time := 20 ns; signal clk, reset : std_logic; -- input signal complete_tick : std_logic; -- output signal count : std_logic_vector(N-1 downto 0); -- output -- total samples to store in file constant num_of_clocks : integer := 30; signal i : integer := 0; -- loop variable file output_buf : text; -- text is keyword begin modMCounter_unit : entity work.modMCounter generic map (M => M, N => N) port map (clk=>clk, reset=>reset, complete_tick=>complete_tick, count=>count); -- reset = 1 for first clock cycle and then 0 reset <= '1', '0' after T/2; -- continuous clock process begin clk <= '0'; wait for T/2; clk <= '1'; wait for T/2; -- store 30 samples in file if (i = num_of_clocks) then file_close(output_buf); wait; else i <= i + 1; end if; end process; -- save data in file : path is relative to Modelsim-project directory file_open(output_buf, "input_output_files/counter_data.csv", write_mode); process(clk) variable write_col_to_output_buf : line; -- line is keyword begin if(clk'event and clk='1' and reset /= '1') then -- avoid reset data -- comment below 'if statement' to avoid header in saved file if (i = 0) then write(write_col_to_output_buf, string'("clock_tick,count")); writeline(output_buf, write_col_to_output_buf); end if; write(write_col_to_output_buf, complete_tick); write(write_col_to_output_buf, string'(",")); -- Note that unsigned/signed values can not be saved in file, -- therefore change into integer or std_logic_vector etc. -- following line saves the count in integer format write(write_col_to_output_buf, to_integer(unsigned(count))); writeline(output_buf, write_col_to_output_buf); end if; end process; end arch;
Hình 1‑6. Kết quả mô phỏng bằng testbench trong Bảng 1‑18.
Hình 1‑7 Kết quả được lưu bằng testbench trong Bảng 1‑18.