понедельник, 3 февраля 2014 г.

Деление на Verilog

Я уже рассматривал операции сложения, вычитания и умножения. Так вышло, что операция деления не является базовой в языке 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++
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

1 комментарий: