Skip to content

Instantly share code, notes, and snippets.

@fl4shk
Last active October 15, 2017 17:15
Show Gist options
  • Save fl4shk/2c7df3920356a6a47db13091aa4181e3 to your computer and use it in GitHub Desktop.
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)
`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
@wgthompson
Copy link

well done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment