Я уже рассматривал операции сложения, вычитания и умножения. Так вышло, что операция деления не является базовой в языке Verilog, поэтому нельзя делить одну регистровую переменную на другую. Сегодня я попытаюсь понять, почему так произошло, и как делить в целочисленных.
Первым делом я начал гуглить этот вопрос и наткнулся на довольно интересную страницу, где описывается умножение и деление на Verilog. Сразу понимания сути это описание не принесло, поэтому стал искать книгу из списка литературы "Computer Organization & Design". И вот в этой книге уже довольно подробно расписано что и почему. Очень советую полистать книгу на досуге.
Сначала определим с чем мы будем иметь дело по ходу работы. У нас будут делимое (divident), делитель (divider), частное (quotient) и остаток (reminder). Причем делимое = делитель*частное + остаток. Для простоты будем рассматривать случай беззнаковых 16 битных целых чисел. Алгоритм выглядит следующим образом:
0. Инициализируем частное = 0. Поскольку каждую итерацию алгоритма делитель нужно будет сдвигать на один разряд вправо, то сделаем 32 битную "копию" делителя, добавив в младшие разряды 16 нулей. Остаток приравниваем к делимому. Дальше весь алгоритм завязан на частном, делителе и остатке. Алгоритм займет N+1 итерацию, где N - количество разрядов делимого и в нашем случае равно шестнадцати.
1. Остаток = остаток - делитель. Если остаток меньше нуля, то вернуть прежний остаток (остаток = остаток + делитель), сдвинуть частное влево на один разряд и в младший разряд подставить 0.
2. Сдвинуть делитель вправо на один разряд. Повторить п. 1.
3..17. Посторить п.2.
Рассмотрим пример из книжки. Поделим 8 битное число 7 на 4 битное число 2.
divident = 7 = 0000 0111;
divider = 2 = 0010;
0. Инициализация:
quotient = 0000;
divider = 0010 0000;
reminder = 0000 0111;
1.
reminder = reminder - divider = 1110 0111 < 0;
reminder = 0000 0111;
quotient = quotient << 1 + 0 = 0000;
2.
divider = divider >> 1 = 0001 0000;
reminder = reminder - divider = 11110111 < 0;
reminder = 0000 0111;
quotient = quotient << 1 + 0 = 0000;
3.
divider = divider >> 1 = 0000 1000;
reminder = reminder - divider = 11111111 < 0;
reminder = 0000 0111;
quotient = quotient << 1 + 0 = 0000;
4.
divider = divider >> 1 = 0000 0100;
reminder = reminder - divider = 0000 0011 > 0;
quotient = quotient << 1 +1 = 0001;
5.
divider = divider >> 1 = 0000 0010;
reminder = reminder - divider = 0000 0001 > 0;
quotient = quotient << 1 +1 = 0011;
end
Алгоритм получается простым, но имеет достаточно много итераций. Вот тут интересная статья про проектирование делителя, в том числе есть простая реализация для случая, когда нужно только частное.
Ну и теперь реализация на Verilog. Количество итераций здесь равно разрядности делимого и получено это благодаря тому, что числа беззнаковые. Благодаря этому можно объединить 0 итерация с 1:
Exported from Notepad++
Первым делом я начал гуглить этот вопрос и наткнулся на довольно интересную страницу, где описывается умножение и деление на Verilog. Сразу понимания сути это описание не принесло, поэтому стал искать книгу из списка литературы "Computer Organization & Design". И вот в этой книге уже довольно подробно расписано что и почему. Очень советую полистать книгу на досуге.
Сначала определим с чем мы будем иметь дело по ходу работы. У нас будут делимое (divident), делитель (divider), частное (quotient) и остаток (reminder). Причем делимое = делитель*частное + остаток. Для простоты будем рассматривать случай беззнаковых 16 битных целых чисел. Алгоритм выглядит следующим образом:
0. Инициализируем частное = 0. Поскольку каждую итерацию алгоритма делитель нужно будет сдвигать на один разряд вправо, то сделаем 32 битную "копию" делителя, добавив в младшие разряды 16 нулей. Остаток приравниваем к делимому. Дальше весь алгоритм завязан на частном, делителе и остатке. Алгоритм займет N+1 итерацию, где N - количество разрядов делимого и в нашем случае равно шестнадцати.
1. Остаток = остаток - делитель. Если остаток меньше нуля, то вернуть прежний остаток (остаток = остаток + делитель), сдвинуть частное влево на один разряд и в младший разряд подставить 0.
2. Сдвинуть делитель вправо на один разряд. Повторить п. 1.
3..17. Посторить п.2.
Рассмотрим пример из книжки. Поделим 8 битное число 7 на 4 битное число 2.
divident = 7 = 0000 0111;
divider = 2 = 0010;
0. Инициализация:
quotient = 0000;
divider = 0010 0000;
reminder = 0000 0111;
1.
reminder = reminder - divider = 1110 0111 < 0;
reminder = 0000 0111;
quotient = quotient << 1 + 0 = 0000;
2.
divider = divider >> 1 = 0001 0000;
reminder = reminder - divider = 11110111 < 0;
reminder = 0000 0111;
quotient = quotient << 1 + 0 = 0000;
3.
divider = divider >> 1 = 0000 1000;
reminder = reminder - divider = 11111111 < 0;
reminder = 0000 0111;
quotient = quotient << 1 + 0 = 0000;
4.
divider = divider >> 1 = 0000 0100;
reminder = reminder - divider = 0000 0011 > 0;
quotient = quotient << 1 +1 = 0001;
5.
divider = divider >> 1 = 0000 0010;
reminder = reminder - divider = 0000 0001 > 0;
quotient = quotient << 1 +1 = 0011;
end
Алгоритм получается простым, но имеет достаточно много итераций. Вот тут интересная статья про проектирование делителя, в том числе есть простая реализация для случая, когда нужно только частное.
Ну и теперь реализация на Verilog. Количество итераций здесь равно разрядности делимого и получено это благодаря тому, что числа беззнаковые. Благодаря этому можно объединить 0 итерация с 1:
module divider
#(parameter N = 32)
(
input wire clk,
input wire start,
input wire [ N-1 : 0 ] divident,
input wire [ N-1 : 0 ] divider,
output wire [ N-1 : 0 ] quotient,
output wire [ N-1 : 0 ] reminder,
output wire ready
);
localparam M = 2*N;
reg signed [ N-1 : 0 ] r_quotient = {N{1'b0}};
assign quotient = r_quotient;
reg signed [ M-1 : 0 ] divident_copy = {M{1'b0}};
reg signed [ M-1 : 0 ] divider_copy = {M{1'b0}};;
wire signed [ M-1 : 0 ] w_diff = divident_copy - divider_copy;
reg [ 5 : 0 ] cnt = 6'b0;
assign reminder = divident_copy[N-1:0];
assign ready = cnt == 0;
always@(posedge clk)
if(ready && start)
begin
cnt <= 6'd32;
r_quotient <= {N{1'b0}};
divident_copy <= {{N{1'b0}}, divident};
divider_copy <= {1'b0, divider, {N-1{1'b0}}};
end
else
begin
cnt <= cnt - 1'b1;
divider_copy <= divider_copy >> 1;
if(!w_diff[63])
begin
divident_copy <= w_diff;
r_quotient <= {quotient[30:0], 1'b1};
end
else
begin
r_quotient <= {quotient[30:0], 1'b0};
end
end
endmodule
а как написать test_bench для данного кода?
ОтветитьУдалить