Простое арифметико-логическое устройство (ALU) на Verilog
Задание. Спроектировать арифметико-логическое устройство с восьмиразрядным входом данных, выполняющее следующие операции: 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.