ALU на Verilog

Простое арифметико-логическое устройство (ALU) на Verilog
Пример написания reusable-кода к данной статье.
Задание. Спроектировать арифметико-логическое устройство с восьмиразрядным входом данных, выполняющее следующие операции: not(А+В), not(А*В), А+В+1, (А + notВ)+1.
Начнем с обдумывания задачи.
Одну из выполняемых устройством операций можно упростить. (А + notВ)+1 это то же самое, что вычитание, поскольку –B=(notB+1). Таким образом четвертой операцией будет: A-B.
Пусть у устройства будет два информационных входа (А и В) и один выход(результат). Тогда для выбора конкретной выполняемой операции из четырех возможных потребуется еще один «вход выбора». Пусть кодирование будет двоичным, тогда разрядность этого входа – 2 бита.
Осталось определить разрядность выхода. Конечно, можно было сделать ее параметрической, однако в таком случае при некоторых операциях и маленькой разрядности выхода будет возникать переполнение, а значит потребуется еще один выход, что очевидно повлечет за собой усложнение модуля. Но модуль может использоваться по-разному и заведомо усложнять его не стоит. С другой стороны, если результат появляется на N-разрядном выходе, то всегда можно обрезать разрядность и вывести переполнение отдельно. Такой подход предпочтительнее, поскольку он является более гибким и более параметрическим.
Тогда нужно определиться с максимальной разрядностью выхода. Очевидно, что среди всех 4х операций, наибольшую разрядность будет иметь умножение not(А*В), а как известно результат умножения числа в [A] бит на число в [B] бит будет числом размером в [A]+[B] бит, это следствие из теории чисел.
Следовательно, выход будет иметь размерность [A]+[B] бит.
Далее представлен листинг Verilog-описания
Листинг программы ALU.v
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 |
// ALU.v // -> Module performs signed arithmetic. // Both input numbers and result are interpreted as signed. // -> Operations are performed on <Clk> negedge. ALU is NOT pipelined. // -> Module can perform 4 operations, depending on how <Mode> bits are set: // ________________________________________ // | <Mode> | | // | [1] [0] | Operation | // |----------------------------------------| // | 0 0 | invsum: Out = not(A+B) | // | 0 1 | invmul: Out = not(A*B) | // | 1 0 | suminc: Out = A+B+1 | // | 1 1 | substr: Out = A-B | // |____________|___________________________| // // Parameters down are explained in instantiation order. // ___________________________________________________________ // | | Default | | // | Parameter | Value | Description | // |-----------------------------------------------------------| // | SIZE_A | 8 | Number of bits needed to store <A> | // | SIZE_B | 8 | Number of bits needed to store <B> | // |____________|_________|____________________________________| // -> The dimension(size) of the <Result> will be A+B bits `ifndef __ALU_V__ `define __ALU_V__ module ALU (Result, A, B, Mode, Clk); parameter SIZE_A = 8, SIZE_B = 8; // Instantiation parameters localparam SIZE_OUT = SIZE_A + SIZE_B; // Dimension of the <Result> //Ports declaration output reg signed [SIZE_OUT-1:0] Result; // result output input wire signed [SIZE_A-1:0] A; // first operand (A) input wire signed [SIZE_B-1:0] B; // second operand (B) input wire [1:0] Mode; // operation selector input wire Clk; // clock input // Internal parameters to improve code readability localparam [1:0] INVSUM = 2'b00, // Out = not(A+B) INVMUL = 2'b01, // Out = not(A*B) SUMINC = 2'b10, // Out = A+B+1 SUBSTR = 2'b11; // Out = A-B // Main procedural block always @(posedge Clk) begin case(Mode) INVSUM : Result <= ~ ( A + B ); INVMUL : Result <= ~ ( A * B ); SUMINC : Result <= A + B + 1'b1; SUBSTR : Result <= A - B; default: Result <= 0; endcase end endmodule `endif |
Как видно из листинга, по умолчанию размер входа А и входа В задан равным 8 бит, это полностью удовлетворяет заданию, но тем не менее код все еще является многократно используемым, ведь при необходимости размерности входных векторов можно переопределить.
Также можно заметить, что основная часть модуля комбинационная, с регистром на выходе(registered output), срабатывающим по заднему фронту сигнала тактирования clk. Для упрощения понимания кода, соответствующие режимы работы ALU были вынесены в localparam.
Теперь разработаем testbench для данного модуля. Сам TB будет состоять из двух файлов: в одном описана вся внутренняя логика тестирования. Другой же файл используется для вызова task’ов, управляющих входами ALU. Такой подход позволяет тестировать модуль, не будучи знакомым с его внутренней структурой.
Листинг ALU_tb_core.v
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 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
// ALU_tb_core.v // // -> Testbench for ALU in behavioral description (file: ALU.v) // -> It is highly recommended that you see ALU.v file first. // -> Do not change timescale unless you know what you do. // Cause this will require constants recalculation. // -> User-defined testbench parameters are: SIZE_A, SIZE_B, Clk_freq_MHz // for details see parameters' comments below // -> (!) Do NOT change anything, except user-defined parameters. // All test are done using task list in file ALU_tb_list.inc.v // For detailed task calls description, see ALU_tb_list.inc.v `ifndef __ALU_TB_CORE_V__ `define __ALU_TB_CORE_V__ `include "ALU.v" `timescale 1ns/1ps // Don't change this until you know what you do module ALU_testbench; // Core parameters set parameter SIZE_A = 8; // First operand dimension parameter SIZE_B = 8; // Second operand dimension parameter Clk_freq_MHz = 25.175; // Input clock frequency in MHz (real // value type) // (!) Do NOT change any code below until you know what you do // Use tasks instead to perform needed tests // For details, see ALU_tb_list.inc.v // Internal parameter (clock delay in timesteps) for selected frequency localparam _delay_clk = 500 / Clk_freq_MHz; // Dimension of the <Result> localparam SIZE_OUT = SIZE_A + SIZE_B; // Testbench variables declaration wire signed [SIZE_OUT-1:0] Result; // result output reg signed [SIZE_A-1:0] A; // first operand (A) reg signed [SIZE_B-1:0] B; // second operand (B) reg [1:0] Mode; // operation selector reg Clk; // clock input // ALU module instantiation ALU #(SIZE_A, SIZE_B) ALU_DUT (.Result(Result), .A(A), .B(B), .Mode(Mode), .Clk(Clk) ); // Clock generator always #_delay_clk Clk = ~Clk; // Tasks needed to simplify the process of testing task invsum ( input signed [SIZE_A-1:0] this_A, input signed [SIZE_B-1:0] this_B, input real Clk_delays ); begin Mode <= 2'b00; A <= this_A; B <= this_B; #(Clk_delays*_delay_clk*2); show(); end endtask task invmul ( input signed [SIZE_A-1:0] this_A, input signed [SIZE_B-1:0] this_B, input real Clk_delays ); begin Mode <= 2'b01; A <= this_A; B <= this_B; #(Clk_delays*_delay_clk*2); show(); end endtask task suminc ( input signed [SIZE_A-1:0] this_A, input signed [SIZE_B-1:0] this_B, input real Clk_delays ); begin Mode <= 2'b10; A <= this_A; B <= this_B; #(Clk_delays*_delay_clk*2); show(); end endtask task substr ( input signed [SIZE_A-1:0] this_A, input signed [SIZE_B-1:0] this_B, input real Clk_delays ); begin Mode <= 2'b11; A <= this_A; B <= this_B; #(Clk_delays*_delay_clk*2); show(); end endtask reg [6*8:0] _Mode_names [3:0]; task show(); begin _Mode_names[0] = "invsum"; _Mode_names[1] = "invmul"; _Mode_names[2] = "suminc"; _Mode_names[3] = "substr"; $display("%dns\t %s\t%d\t%d\t%d", $time,_Mode_names[Mode],A,B,Result ); end endtask initial begin A=0; B=0; Mode=0; Clk=0; $display("\t\t Time Operation A\t B\tResult"); `include "ALU_tb_list.inc.v" // Include operation tasks call list $stop; end endmodule `endif |
Как видим, здесь частота тактирования задается параметром внутри testbench’a. Весь процесс тестирования производится с помощью вызовов тасков из файла ALU_tb_list.inc.v
Листинг ALU_tb_list.inc.v
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 |
// ALU_tb_list.inc.v // // -> This task call list is made to simplify ALU testing process. // -> It is highly recommended that you see ALU_tb_core.v file first. // -> To set input test vector you need to call a task, and pass // input values to it. See below how it works. // // -> Detailed tasks description (according to ALU.v): // 1) invsum (A, B, Clk_delays) // 2) invmul (A, B, Clk_delays) // 3) suminc (A, B, Clk_delays) // 4) substr (A, B, Clk_delays) // ** Where <A> and <B> are ALU inputs. // ** <Clk_delays> specifies number of clock periods, // until control is returned back to the code flow // // -> When task changes input signals results are logged in sim // -> As code in this file is executed inside the initial block, // you can use any veriloge code here depending on your needs // Check whether file is called by TB core module `ifdef __ALU_TB_CORE_V__ // BEGIN sequential task calls suminc(25, 3, 5); substr(14, 4, 2); invmul(-3, -4, 2); invmul(5, 8, 2); invsum(10, 10, 2); // END `endif |
Ключевым моментом в этом файле является конструкция:
1 2 3 4 5 |
`ifdef __ALU_TB_CORE_V__ . . . . . . . . `endif |
__ALU_TB_CORE_V__ задан define‘ом в основном файле testbench’a из которого производится подключение. Использование такой конструкции необходимо для того, что бы избавиться от ошибок компиляции: если в списке компиляции какого-то симулятора файл ALU_tb_list.inc.v будет находиться раньше, чем файл ALU_tb_core.v, то ошибка уже не возникнет. То есть происходит проверка, действительно ли компиляция дочернего файла вызвана необходимостью компиляции родительского.
Автор: Ходнев Т.А., ДК-11.