Цифровой автомат на Verilog

Цифровой автомат на Verilog с использованием подхода многократно используемого кода
Данная статья является иллюстрацией к статье о применении многократно используемого кода при программировании на Verilog.
Задание: имеется следующий код (описан ниже), описывающий цифровой автомат. Необходимо построить testbench для проверки правильности его работы.
Листинг исходного цифрового автомата
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
module state_mach (clk, reset, Input1, Input2, output1); input clk, reset, Input1, Input2; output output1; reg output1; reg [1:0] state; /* Кодирование состояний автомата*/ parameter [1:0] state_A=0, state_B=1, state_C=2; always @ (posedge clk or posedge reset) begin if (reset) state = state_A; else /*Определение следующего состояния автомата*/ case(state) state_A: if (Input1 ==0) state = state_B; else state = state_C; state_B: state = state_C; state_C: if (Input2) state = state_A; default: state = state_A; endcase end /*Состояние выхода конечного автомата*/ always @(state) begin case (state) state_A: output1 = 0; state_B: output1 = 1; state_C: output1 = 0; default: output1 = 0; endcase end endmodule |
Как и в предыдущем примере, начнем с простых размышлений.
Говоря о способах описания цифровых автоматов на Verilog HDL, следует отметить что до тех пор, пока логика, генерирующая состояние выхода цифрового автомата не вынесена в отдельный always-блок, создание цифрового автомата Мили невозможно, поскольку на выходе всегда будут flip-flop’ы. Описывать автомат Мили, вообще говоря, рекомендуется в три always-блока.
В данном случае, хоть выходы и генерируются в отдельном блоке, данный автомат является автоматом Мура.
Говоря о testbench’ах, принято выделять три типа тестирования:
-
Тестирование «белых ящиков»(White box). В данном случае код тестируемого модуля виден тестировщику и доступен для изменения. Данная ситуация является наиболее благоприятной, и позволяет провести тестирование наилучшим образом.
-
Тестирование «серых ящиков»(Gray box). Код модуля виден тестировщику, но НЕ досупен для изменения. Такая ситуация возможна, например, если лицензия, согласно которой происходит расспостранение кода, не допускает внесение в него изменений
-
Тестирование «черных ящиков»(Black box). Тестировщику вообще не доступен исходный код, он может лишь догадываться о способах реализации какого-либо модуля. Такая ситуация довольно редкая, однако возможна, например при тестировании пропиетарных технологий или, например, передачи данных по зашифрованному приватным ключом каналу.
При тестировании довольно важным параметром является покрытие в testbench’e возможных управляющих комбинаций(combinational coverage). Однако в сложных устройствах полного покрытия добиться очень сложно: это требует значительных вычислительных ресурсов и может занимать годы на больших вычислительных кластерах. Поэтому в сложных проектах применяются другие подходы, например, вероятностный.
В данном случае исходный модуль рассматривается в качестве серого ящика. Поскольку конечный автомат довольно простой, такое тестирование вполне допустимо и вполне может иметь 100% покрытия состояний конечного автомата(FSM state coverage), что в свою очередь полностью определяет правильность его работы.
Следующий код не вносит никаких изменений в исходный код тестируемого модуля, однако сам написан с учетом этого исходного кода.
Листинг основного файла testbench’a для стейт машины на Verilog
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
// example_tb.v // // -> Testbench is used to test FSM module in "example.v" // -> Assuming that FSM acts as a gray box with following signal lines: // _____________________________________________________ // | | | | // | Signal | Dir | Description | // |-----------------------------------------------------| // | clk | in | clock input | // | reset | in | async reset, active high | // | Input1 | in | 1st control signal, active high | // | Input2 | in | 2nd control signal, active high | // | output1 | out | output (registered) | // |_________|_____|_____________________________________| // // -> Testbench has following parameters: // ___________________________________________________________ // | | Default | | // | Parameter | Value | Description | // |-------------------------------------------------------------| // | Clk_freq_MHz | 10.25 | DUT clk frequency. Has type REAL | // | state_A | 0 | FSM state encoding. BIN by default | // | state_B | 1 | --You can set your own like GRAY, | // | state_C | 2 | --JOHNSON or ONE-HOT if you want | // |______________|_________|____________________________________| `ifndef __EXAMPLE_TB__ `define __EXAMPLE_TB__ `include "example.v" `timescale 1ns/1ps // Don't change this until you know what you do module example_TB; // User defined parameters parameter Clk_freq_MHz = 10.25; // Set your clk frequency here parameter [1:0] state_A=0, // FSM state encoding state_B=1, // FSM state encoding state_C=2; // FSM state encoding // (!) Do NOT change any code below until you know what you do // All tests are performed using tasks, // Be sure you see file "example_tb_calls.inc.v" first // Internal parameter to generate clk with selected frequency localparam _clk_timesteps = 500 / Clk_freq_MHz; // Testbench variables declaration reg _clk; // clock input reg _reset; // fsm reset input reg _input1; // control signal 1 input reg _input2; // control signal 2 input wire _output1; // registered fsm output // DUT module instantiation state_mach #(state_A, state_B, state_C) FSM_DUT (.clk(_clk), .reset(_reset), .Input1(_input1), .Input2(_input2), .output1(_output1) ); // Generate clock with selected frequency always #(_clk_timesteps) _clk = ~_clk; // begin TB tasks section real N; reg val; task reset(input real N); begin _reset=1'b1; // reset pulse begin #N; // wait interval in nanoseconds _reset=1'b0; // reset pulse end end endtask task delay(input real N); begin #(_clk_timesteps*2*N); end endtask task in1(input reg val); begin _input1=val; end endtask task in2(input reg val); begin _input2=val; end endtask // end TB tasks section // task calls sequence initial begin $display("-> Simulation started"); // set initial values #0 _clk=1'b0; _reset=1'b0; _input1=1'b0; _input2=1'b0; // monitor value changes $display("\t\t Time\tIn1\tIn2\tOut"); $monitor("%dns\t%b\t%b\t%b", $time, _input1, _input2, _output1); // include user task calls `include "example_tb_calls.inc.v" // finish testbench sequence $display("-> Testbench simulation stopped at %d ns", $time); $stop; end endmodule //example_TB `endif //example_tb.v |
В вышеприведенном testbench’e применен примерно такой же подход как и в TB из предыдущего примера, поскольку он практически идеально справляется с предоставлением удобного интерфейса для тестирования.
Листинг файла вызовов тестбенчей для ЦА
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// example_tb_calls.inc.v // // // -> Here task calls are used. // Any other possible verilog constructions can be used too. // // -> Tasks list: // * reset(input real N) // ----put FSM in reset for N nanosec // * delay(input real N) // ----do nothing for N clock cycles // * in1(input reg V) // ----set <input1> value to V // * in2(input reg V) // ----set <input2> value to V `ifdef __EXAMPLE_TB__ reset(20); delay(5); in1(1); delay(10); in1(0); delay(5); `endif |
Данный testbench можно адаптировать для тестирования, например, следующего конечного автомата:
Листинг
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
`ifndef __FSM_1__ `define __FSM_1__ module FSM1( input wire clk, input wire rst, input wire en, output reg max ); // FSM states description. One-Hot Encoding localparam [3:0] state_A=0, state_B=1, state_C=2, state_D=3; reg [3:0] state, next; // state register always @(posedge clk, posedge rst) begin if(rst) begin state<=4'b0; state[state_A]<=1'b1; end else state<=next; end // next state logic with output logic always @* begin next=4'b0; // to catch unset states during simulation max=1'b0; case(state) 1'b1<<state_A : begin if(en) next[state_B]=1'b1; else next[state_A]=1'b1; end 1'b1<<state_B : begin if(en) next[state_C]=1'b1; else next[state_B]=1'b1; end 1'b1<<state_C : begin if(en) next[state_D]=1'b1; else next[state_C]=1'b1; end 1'b1<<state_D : begin if(en) begin max=1'b1; next[state_B]=1'b1; end else next[state_A]=1'b1; end default : next[state_A]=1'b1; endcase end endmodule `endif |
Автор: Ходнев Т.А., ДК-11.