Last active
October 15, 2017 17:15
-
-
Save fl4shk/2c7df3920356a6a47db13091aa4181e3 to your computer and use it in GitHub Desktop.
Example of Non-Restoring Unsigned or Signed Integer Division in SystemVerilog (compatible with Icarus Verilog's SystemVerilog support, and tested with my Altera Cyclone IV FPGA)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
`define WIDTH_TO_MSB_POS(width) ((width) - 1) | |
// Unsigned (or signed!) integer division | |
// Don't try to do larger than a 128-bit division with this without | |
// increasing counter_msb_pos. | |
// Depending on the FPGA being used and the clock rate, it may be doable to | |
// perform more than one iterate() per cycle, obtaining faster divisions. | |
// For obvious reasons, this does not return a correct result upon division | |
// by zero. | |
module NonRestoringDivider #(parameter args_width=32) | |
(input wire clk, enable, unsgn_or_sgn, | |
// Numerator, Denominator | |
input bit [`WIDTH_TO_MSB_POS(args_width):0] num, denom, | |
// Quotient, Remainder | |
output bit [`WIDTH_TO_MSB_POS(args_width):0] quot, rem, | |
output bit can_accept_cmd, data_ready); | |
parameter args_msb_pos = `WIDTH_TO_MSB_POS(args_width); | |
parameter temp_width = (args_width << 1) + 1; | |
parameter temp_msb_pos = `WIDTH_TO_MSB_POS(temp_width); | |
// This assumes you aren't trying to do division of numbers larger than | |
// 128-bit. | |
parameter counter_msb_pos = 7; | |
bit [counter_msb_pos:0] __counter, __state_counter; | |
bit [args_msb_pos:0] __num_buf, __denom_buf; | |
bit [args_msb_pos:0] __quot_buf, __rem_buf; | |
wire __busy; | |
wire __num_is_negative, __denom_is_negative; | |
bit __num_was_negative, __denom_was_negative; | |
bit __unsgn_or_sgn_buf; | |
// Temporaries | |
bit [temp_msb_pos:0] __P; | |
bit [temp_msb_pos:0] __D; | |
// Tasks | |
task iterate; | |
// if (__P >= 0) | |
if (!__P[temp_msb_pos] || (__P == 0)) | |
begin | |
__quot_buf[__counter] = 1; | |
__P = (__P << 1) - __D; | |
end | |
else | |
begin | |
__quot_buf[__counter] = 0; | |
__P = (__P << 1) + __D; | |
end | |
__counter = __counter - 1; | |
endtask | |
// Assignments | |
assign __busy = !can_accept_cmd; | |
assign __num_is_negative = $signed(num) < $signed(0); | |
assign __denom_is_negative = $signed(denom) < $signed(0); | |
initial | |
begin | |
__counter = 0; | |
__state_counter = 0; | |
__P = 0; | |
__D = 0; | |
__state_counter = 0; | |
quot = 0; | |
rem = 0; | |
can_accept_cmd = 1; | |
data_ready = 0; | |
end | |
always @ (posedge clk) | |
begin | |
if (__state_counter[counter_msb_pos]) | |
begin | |
__quot_buf = 0; | |
__rem_buf = 0; | |
__counter = args_msb_pos; | |
__P = __num_buf; | |
__D = __denom_buf << args_width; | |
end | |
else if (__busy) | |
begin | |
//if (!__state_counter[counter_msb_pos]) | |
if ($signed(__counter) > $signed(-1)) | |
begin | |
// At some clock rates, some FPGAs may be able to handle | |
// more than one iteration per clock cycle, which is why | |
// iterate() is a task. Feel free to try more than one | |
// iteration per clock cycle. | |
iterate(); | |
end | |
end | |
end | |
always @ (posedge clk) | |
begin | |
if (enable && can_accept_cmd) | |
begin | |
can_accept_cmd <= 0; | |
data_ready <= 0; | |
__state_counter <= -1; | |
__num_buf <= (unsgn_or_sgn && __num_is_negative) | |
? (-num) : num; | |
__denom_buf <= (unsgn_or_sgn && __denom_is_negative) | |
? (-denom) : denom; | |
__unsgn_or_sgn_buf <= unsgn_or_sgn; | |
__num_was_negative <= __num_is_negative; | |
__denom_was_negative <= __denom_is_negative; | |
end | |
else if (__busy) | |
begin | |
if (!__counter[counter_msb_pos]) | |
begin | |
__state_counter <= __state_counter + 1; | |
end | |
else | |
begin | |
can_accept_cmd <= 1; | |
__state_counter <= -1; | |
data_ready <= 1; | |
//$display("end: %d, %d %d, %d", | |
// __unsgn_or_sgn_buf, | |
// __num_was_negative, __denom_was_negative, | |
// (__num_was_negative ^ __denom_was_negative)); | |
if (__P[temp_msb_pos]) | |
begin | |
quot <= (__unsgn_or_sgn_buf | |
&& (__num_was_negative ^ __denom_was_negative)) | |
? (-((__quot_buf - (~__quot_buf)) - 1)) | |
: ((__quot_buf - (~__quot_buf)) - 1); | |
rem <= (__unsgn_or_sgn_buf && __num_was_negative) | |
? (-((__P + __D) >> args_width)) | |
: ((__P + __D) >> args_width); | |
end | |
else | |
begin | |
quot <= (__unsgn_or_sgn_buf | |
&& (__num_was_negative ^ __denom_was_negative)) | |
? (-((__quot_buf - (~__quot_buf)))) | |
: ((__quot_buf - (~__quot_buf))); | |
rem <= (__unsgn_or_sgn_buf && __num_was_negative) | |
? (-((__P) >> args_width)) | |
: ((__P) >> args_width); | |
end | |
end | |
end | |
end | |
endmodule | |
`define DIV_WIDTH 8 | |
`define DIV_MSB_POS `WIDTH_TO_MSB_POS(`DIV_WIDTH) | |
`define MASTER_CLOCK_DELAY #1 | |
`define CLOCK_DELAY #2 | |
module UnsignedTestBench(input bit clk, enable); | |
// Used to keep Icarus Verilog happy | |
bit __x; | |
integer i, j; | |
integer num_wrong_quots, num_wrong_rems; | |
// Connections to the divider | |
bit div_enable; | |
wire div_unsgn_or_sgn; | |
bit [`DIV_MSB_POS:0] div_num, div_denom; | |
wire [`DIV_MSB_POS:0] div_quot, div_rem; | |
// Theoretically correct outputs | |
bit [`DIV_MSB_POS:0] tc_quot, tc_rem; | |
wire div_can_accept_cmd, div_data_ready; | |
// Indicates signed divider is wanted | |
assign div_unsgn_or_sgn = 0; | |
initial | |
begin | |
i = 0; | |
j = 0; | |
num_wrong_quots = 0; | |
num_wrong_rems = 0; | |
end | |
initial | |
begin | |
if (enable) | |
begin | |
//for (i=0; i<32'h0000_0100; i=i+1) | |
//for (i=0; i<32'h0000_0400; i=i+1) | |
for (i=0; i<(1 << `DIV_WIDTH); i=i+1) | |
begin | |
//for (j=0; j<32'h0000_0100; j=j+1) | |
//for (j=0; j<32'h0000_0400; j=j+1) | |
for (j=0; j<(1 << `DIV_WIDTH); j=j+1) | |
begin | |
div_num = i; | |
div_denom = j; | |
while (!div_can_accept_cmd) | |
begin | |
`CLOCK_DELAY | |
__x = 1; | |
//$display("!div_can_accept_cmd"); | |
end | |
`CLOCK_DELAY | |
div_enable = 1; | |
`CLOCK_DELAY | |
div_enable = 0; | |
while (!div_data_ready) | |
begin | |
`CLOCK_DELAY | |
__x = 1; | |
//$display("!div_data_ready"); | |
end | |
tc_quot = div_num / div_denom; | |
tc_rem = div_num % div_denom; | |
//$display("Results for %d / %d: %d, %d: TC: %d, %d", | |
// div_num, div_denom, div_quot, div_rem, tc_quot, | |
// tc_rem); | |
if (div_denom != 0) | |
begin | |
if ((tc_quot != div_quot) || (tc_rem != div_rem)) | |
begin | |
$display("Wrong results: %d / %d: %d,%d: ", | |
div_num, div_denom, div_quot, div_rem); | |
$display("Theoretically %s%d, %d", | |
"correct stuff: ", tc_quot, tc_rem); | |
if (tc_quot != div_quot) | |
begin | |
num_wrong_quots = num_wrong_quots + 1; | |
$display("Wrong quotient!"); | |
end | |
if (tc_rem != div_rem) | |
begin | |
num_wrong_rems = num_wrong_rems + 1; | |
$display("Wrong remainder!\n"); | |
end | |
end | |
end | |
else | |
begin | |
$display("Since the denominator was zero, %s", | |
"expect incorrect results!"); | |
end | |
`CLOCK_DELAY | |
div_enable = 0; | |
end | |
end | |
`CLOCK_DELAY | |
$display("Num incorrect results: %d, %d", num_wrong_quots, | |
num_wrong_rems); | |
$finish; | |
end | |
end | |
NonRestoringDivider #(`DIV_WIDTH) divider(.clk(clk), | |
.enable(div_enable), .unsgn_or_sgn(div_unsgn_or_sgn), | |
.num(div_num), .denom(div_denom), | |
.quot(div_quot), .rem(div_rem), | |
.can_accept_cmd(div_can_accept_cmd), .data_ready(div_data_ready)); | |
endmodule | |
module SignedTestBench(input bit clk, enable); | |
// Used to keep Icarus Verilog happy | |
bit __x; | |
integer i, j; | |
integer num_wrong_quots, num_wrong_rems; | |
// Connections to the divider | |
bit div_enable; | |
wire div_unsgn_or_sgn; | |
bit [`DIV_MSB_POS:0] div_num, div_denom; | |
wire [`DIV_MSB_POS:0] div_quot, div_rem; | |
// Theoretically correct outputs | |
bit [`DIV_MSB_POS:0] tc_quot, tc_rem; | |
wire div_can_accept_cmd, div_data_ready; | |
// Indicates signed divider is wanted | |
assign div_unsgn_or_sgn = 1; | |
initial | |
begin | |
i = 0; | |
j = 0; | |
num_wrong_quots = 0; | |
num_wrong_rems = 0; | |
end | |
initial | |
begin | |
if (enable) | |
begin | |
//for (i=0; i<32'h0000_0100; i=i+1) | |
//for (i=-128; i<128; i=i+1) | |
for (i=-(1 << `DIV_MSB_POS); i<(1 << `DIV_MSB_POS); i=i+1) | |
begin | |
//for (j=-128; j<128; j=j+1) | |
for (j=-(1 << `DIV_MSB_POS); j<(1 << `DIV_MSB_POS); j=j+1) | |
begin | |
div_num = $signed(i); | |
div_denom = $signed(j); | |
while (!div_can_accept_cmd) | |
begin | |
`CLOCK_DELAY | |
__x = 1; | |
//$display("!div_can_accept_cmd"); | |
end | |
`CLOCK_DELAY | |
div_enable = 1; | |
`CLOCK_DELAY | |
div_enable = 0; | |
while (!div_data_ready) | |
begin | |
`CLOCK_DELAY | |
__x = 1; | |
//$display("!div_data_ready"); | |
end | |
tc_quot = $signed(div_num) / $signed(div_denom); | |
tc_rem = $signed(div_num) % $signed(div_denom); | |
//$display("Results for %d / %d: %d, %d: TC: %d, %d", | |
// $signed(div_num), $signed(div_denom), | |
// $signed(div_quot), $signed(div_rem), | |
// $signed(tc_quot), $signed(tc_rem)); | |
if (div_denom != 0) | |
begin | |
if (($signed(tc_quot) != $signed(div_quot)) | |
|| ($signed(tc_rem) != $signed(div_rem))) | |
begin | |
$display("Wrong results: %d / %d: %d,%d: ", | |
$signed(div_num), $signed(div_denom), | |
$signed(div_quot), $signed(div_rem)); | |
$display("Theoretically %s%d, %d", | |
"correct stuff: ", $signed(tc_quot), | |
$signed(tc_rem)); | |
if ($signed(tc_quot) != $signed(div_quot)) | |
begin | |
num_wrong_quots = num_wrong_quots + 1; | |
$display("Wrong quotient!"); | |
end | |
if ($signed(tc_rem) != $signed(div_rem)) | |
begin | |
num_wrong_rems = num_wrong_rems + 1; | |
//$display("Wrong remainder!\n"); | |
//$display("Wrong remainder!"); | |
end | |
$display("\n"); | |
end | |
end | |
else | |
begin | |
$display("Since the denominator was zero, %s", | |
"expect incorrect results!"); | |
end | |
`CLOCK_DELAY | |
div_enable = 0; | |
end | |
end | |
`CLOCK_DELAY | |
$display("Num incorrect results: %d, %d", num_wrong_quots, | |
num_wrong_rems); | |
$finish; | |
end | |
end | |
NonRestoringDivider #(`DIV_WIDTH) divider(.clk(clk), | |
.enable(div_enable), .unsgn_or_sgn(div_unsgn_or_sgn), | |
.num(div_num), .denom(div_denom), | |
.quot(div_quot), .rem(div_rem), | |
.can_accept_cmd(div_can_accept_cmd), .data_ready(div_data_ready)); | |
endmodule | |
module TopLevel; | |
bit __clk; | |
wire __unsigned_tb_enable, __signed_tb_enable; | |
//assign __unsigned_tb_enable = 1; | |
assign __unsigned_tb_enable = 0; | |
assign __signed_tb_enable = 1; | |
//assign __signed_tb_enable = 0; | |
initial | |
begin | |
__clk = 0; | |
end | |
// Clock generator | |
always | |
begin | |
`MASTER_CLOCK_DELAY | |
__clk = !__clk; | |
end | |
UnsignedTestBench unsigned_tb(.clk(__clk), | |
.enable(__unsigned_tb_enable)); | |
SignedTestBench signed_tb(.clk(__clk), .enable(__signed_tb_enable)); | |
endmodule |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
well done