diff --git a/doc/components/divider.md b/doc/components/divider.md index 5a678d6b..a2ebb689 100644 --- a/doc/components/divider.md +++ b/doc/components/divider.md @@ -36,6 +36,8 @@ For the division, implicit rounding towards 0 is always performed. I.e., a negat For the remainder, the following equation will always precisely hold true: `dividend = divisor * quotient + remainder`. Note that this differs from the Euclidean modulo operator where the sign of the remainder is always positive. +Overflow can only occur when `dividend=`, `divisor=-1` and `isSigned=1`. In this case, the hardware will return `quotient=` and `remainder=0`. This is by design as the mathematically correct quotient cannot be represented in the fixed number of bits available. + ## Code Example ```dart diff --git a/lib/src/arithmetic/divider.dart b/lib/src/arithmetic/divider.dart index 1281badf..c3016eee 100644 --- a/lib/src/arithmetic/divider.dart +++ b/lib/src/arithmetic/divider.dart @@ -168,9 +168,9 @@ class MultiCycleDivider extends Module { void _build() { // to capture current inputs // as this operation takes multiple cycles - final aBuf = Logic(name: 'aBuf', width: dataWidth); - final rBuf = Logic(name: 'rBuf', width: dataWidth); - final bBuf = Logic(name: 'bBuf', width: dataWidth); + final aBuf = Logic(name: 'aBuf', width: dataWidth + 1); + final rBuf = Logic(name: 'rBuf', width: dataWidth + 1); + final bBuf = Logic(name: 'bBuf', width: dataWidth + 1); final signOut = Logic(name: 'signOut'); final signNum = Logic(name: 'signNum'); @@ -183,27 +183,30 @@ class MultiCycleDivider extends Module { // internal buffers for computation // accumulator that contains dividend - final outBuffer = Logic(name: 'outBuffer', width: dataWidth); + final outBuffer = Logic(name: 'outBuffer', width: dataWidth + 1); // capture last successful power of 2 - final lastSuccess = Logic(name: 'lastSuccess', width: dataWidth); + final lastSuccess = Logic(name: 'lastSuccess', width: dataWidth + 1); // combinational logic signal to compute current (a-b*2^i) - final tmpDifference = Logic(name: 'tmpDifference', width: dataWidth); + final tmpDifference = Logic(name: 'tmpDifference', width: dataWidth + 1); // store last diff - final lastDifference = Logic(name: 'lastDifference', width: dataWidth); + final lastDifference = Logic(name: 'lastDifference', width: dataWidth + 1); // combinational logic signal to check for overflow when shifting - final tmpShift = Logic(name: 'tmpShift', width: dataWidth); + final tmpShift = Logic(name: 'tmpShift', width: dataWidth + 1); // current value of i to try // need log(dataWidth) bits final currIndex = Logic(name: 'currIndex', width: logDataWidth); - intf.quotient <= outBuffer; // result is ultimately stored in out_buffer + intf.quotient <= + outBuffer.getRange( + 0, dataWidth); // result is ultimately stored in out_buffer intf.divZero <= ~bBuf.or(); // divide-by-0 if b==0 (NOR) - intf.remainder <= rBuf; // synonymous with the remainder + intf.remainder <= + rBuf.getRange(0, dataWidth); // synonymous with the remainder // ready/busy signals are based on internal state intf.validOut <= currentState.eq(_MultiCycleDividerState.done); - intf.readyIn <= ~currentState.eq(_MultiCycleDividerState.ready); + intf.readyIn <= currentState.eq(_MultiCycleDividerState.ready); // update current_state with next_state once per cycle Sequential(intf.clk, [ @@ -249,13 +252,7 @@ class MultiCycleDivider extends Module { ~bBuf.getRange(0, dataWidth - 2).or() & (signOut ^ signNum), then: [ - // special logic for when a is also most negative # - tmpDifference < - mux( - aBuf[dataWidth - 1] & - ~aBuf.getRange(0, dataWidth - 2).or(), - ~Const(0, width: dataWidth), // -1 - Const(0, width: dataWidth)), + tmpDifference < ~Const(0, width: dataWidth + 1), // -1 nextState < _MultiCycleDividerState.accumulate ], orElse: [ @@ -284,6 +281,12 @@ class MultiCycleDivider extends Module { // capture input arguments a, b into internal buffers // so the consumer doesn't have to continually assert them + final extDividendIn = Logic(name: 'extDividendIn', width: dataWidth + 1) + ..gets(mux(intf.isSigned, intf.dividend.signExtend(dataWidth + 1), + intf.dividend.zeroExtend(dataWidth + 1))); + final extDivisorIn = Logic(name: 'extDivisorIn', width: dataWidth + 1) + ..gets(mux(intf.isSigned, intf.divisor.signExtend(dataWidth + 1), + intf.divisor.zeroExtend(dataWidth + 1))); Sequential(intf.clk, [ If.block([ // only when READY and new inputs are available @@ -297,11 +300,11 @@ class MultiCycleDivider extends Module { // conditionally convert negative inputs to positive // and compute the output sign aBuf < - mux(intf.dividend[dataWidth - 1] & intf.isSigned, - ~intf.dividend + 1, intf.dividend), + mux(extDividendIn[dataWidth - 1] & intf.isSigned, + ~extDividendIn + 1, extDividendIn), bBuf < - mux(intf.divisor[dataWidth - 1] & intf.isSigned, - ~intf.divisor + 1, intf.divisor), + mux(extDivisorIn[dataWidth - 1] & intf.isSigned, + ~extDivisorIn + 1, extDivisorIn), signOut < (intf.dividend[dataWidth - 1] ^ intf.divisor[dataWidth - 1]) & intf.isSigned, @@ -328,7 +331,7 @@ class MultiCycleDivider extends Module { final aBufConv = mux(signNum, ~aBuf + 1, aBuf); Sequential(intf.clk, [ If.block([ - Iff(intf.reset, [rBuf < Const(0, width: dataWidth)]), + Iff(intf.reset, [rBuf < Const(0, width: dataWidth + 1)]), ElseIf( currentState.eq(_MultiCycleDividerState .convert), // adjust positive remainder for signs @@ -365,8 +368,8 @@ class MultiCycleDivider extends Module { ElseIf(currentState.eq(_MultiCycleDividerState.ready) & intf.validIn, [ lastSuccess < 0, lastDifference < - mux(intf.dividend[dataWidth - 1] & intf.isSigned, - ~intf.dividend + 1, intf.dividend), // start by matching aBuf + mux(extDividendIn[dataWidth - 1] & intf.isSigned, + ~extDividendIn + 1, extDividendIn), // start by matching aBuf ]), ElseIf( currentState.eq(_MultiCycleDividerState @@ -374,7 +377,8 @@ class MultiCycleDivider extends Module { [ If(~tmpDifference[dataWidth - 1], then: [ lastSuccess < - (Const(1, width: dataWidth) << currIndex), // capture 2^i + (Const(1, width: dataWidth + 1) << + currIndex), // capture 2^i lastDifference < tmpDifference ], orElse: [ // failure so maintain @@ -397,7 +401,8 @@ class MultiCycleDivider extends Module { If.block([ Iff(intf.reset, [outBuffer < 0]), // reset buffer ElseIf(currentState.eq(_MultiCycleDividerState.done), [ - outBuffer < mux(intf.readyOut, Const(0, width: dataWidth), outBuffer), + outBuffer < + mux(intf.readyOut, Const(0, width: dataWidth + 1), outBuffer), ]), // reset buffer if consumed result ElseIf(currentState.eq(_MultiCycleDividerState.convert), [ outBuffer < mux(signOut, ~outBuffer + 1, outBuffer), diff --git a/test/arithmetic/divider_test.dart b/test/arithmetic/divider_test.dart index 3c06da24..6574b1bf 100644 --- a/test/arithmetic/divider_test.dart +++ b/test/arithmetic/divider_test.dart @@ -122,7 +122,7 @@ class MultiCycleDividerDriver extends Driver { // Every clock negative edge, drive the next pending item if it exists // but only when the DUT isn't busy intf.clk.negedge.listen((args) { - if (_pendingItems.isNotEmpty && intf.readyIn.value == LogicValue.zero) { + if (_pendingItems.isNotEmpty && intf.readyIn.value == LogicValue.one) { final nextItem = _pendingItems.removeFirst(); drive(nextItem); if (_pendingItems.isEmpty) { @@ -167,7 +167,7 @@ class MultiCycleDividerInputMonitor // Every positive edge of the clock intf.clk.posedge.listen((event) { if (intf.validIn.value == LogicValue.one && - intf.readyIn.value == LogicValue.zero) { + intf.readyIn.value == LogicValue.one) { add(MultiCycleDividerInputSeqItem( mDividend: intf.isSigned.value.toBool() ? _twosComp(intf.dividend.value.toInt(), intf.dividend.width) @@ -266,11 +266,22 @@ class MultiCycleDividerScoreboard extends Component { inSign ? _twosComp(currResult, intf.quotient.width) : currResult; final tCurrRemain = inSign ? _twosComp(currRemain, intf.remainder.width) : currRemain; + final overflow = inSign && + in1 == + _twosComp( + 1 << (intf.quotient.width - 1), intf.quotient.width) && + in2 == -1; if (triggerCheck) { - final check1 = (in2 == 0) ? divZero : ((in1 ~/ in2) == tCurrResult); - final check2 = (in2 == 0) + var check1 = (in2 == 0) ? divZero : ((in1 ~/ in2) == tCurrResult); + var check2 = (in2 == 0) ? divZero : ((in1 - (in2 * tCurrResult)) == tCurrRemain); + if (overflow) { + check1 = tCurrResult == + _twosComp(1 << (intf.quotient.width - 1), intf.quotient.width); + check2 = tCurrRemain == 0; + } + if (check1 && check2) { final msg = (in2 == 0) ? ''' @@ -399,6 +410,84 @@ class MultiCycleDividerBasicSequence extends Sequence { } } +class MultiCycleDividerEvilSequence extends Sequence { + late final int numBits; + + MultiCycleDividerEvilSequence( + {required this.numBits, String name = 'MultiCycleDividerEvilSequence'}) + : super(name); + + @override + Future body(Sequencer sequencer) async { + sequencer as MultiCycleDividerSequencer + // largest positive divided by largest positive + ..add(MultiCycleDividerInputSeqItem( + mDividend: (1 << (numBits - 1)) - 1, + mDivisor: (1 << (numBits - 1)) - 1, + mValidIn: true, + mIsSigned: true, + mReadyOut: true)) + // largest positive divided by largest negative + ..add(MultiCycleDividerInputSeqItem( + mDividend: (1 << (numBits - 1)) - 1, + mDivisor: (1 << (numBits - 1)), + mValidIn: true, + mIsSigned: true, + mReadyOut: true)) + // largest negative divided by largest positive + ..add(MultiCycleDividerInputSeqItem( + mDividend: (1 << (numBits - 1)), + mDivisor: (1 << (numBits - 1)) - 1, + mValidIn: true, + mIsSigned: false, + mReadyOut: true)) + // largest negative divided by largest negative + ..add(MultiCycleDividerInputSeqItem( + mDividend: (1 << (numBits - 1)), + mDivisor: (1 << (numBits - 1)), + mValidIn: true, + mIsSigned: false, + mReadyOut: true)) + // largest positive divided by negative 1 + ..add(MultiCycleDividerInputSeqItem( + mDividend: (1 << (numBits - 1)) - 1, + mDivisor: -1, + mValidIn: true, + mIsSigned: true, + mReadyOut: true)) + // largest negative divided by negative 1 + // this is the only true overflow case... + ..add(MultiCycleDividerInputSeqItem( + mDividend: (1 << (numBits - 1)), + mDivisor: -1, + mValidIn: true, + mIsSigned: true, + mReadyOut: true)) + // largest positive divided by 1 + ..add(MultiCycleDividerInputSeqItem( + mDividend: (1 << (numBits - 1)) - 1, + mDivisor: 1, + mValidIn: true, + mIsSigned: true, + mReadyOut: true)) + // largest negative divided by 1 + ..add(MultiCycleDividerInputSeqItem( + mDividend: (1 << (numBits - 1)), + mDivisor: 1, + mValidIn: true, + mIsSigned: true, + mReadyOut: true)) + // unsigned version of overflow case + // which should not result in overflow + ..add(MultiCycleDividerInputSeqItem( + mDividend: (1 << (numBits - 1)), + mDivisor: -1, + mValidIn: true, + mIsSigned: false, + mReadyOut: true)); + } +} + class MultiCycleDividerVolumeSequence extends Sequence { final int numReps; final rng = Random(0xdeadbeef); // fixed seed @@ -479,6 +568,7 @@ class MultiCycleDividerTest extends Test { // Kick off a sequence on the sequencer await _divSequencer.start(MultiCycleDividerBasicSequence()); + await _divSequencer.start(MultiCycleDividerEvilSequence(numBits: 32)); await _divSequencer.start(MultiCycleDividerVolumeSequence(1000)); logger.info('Done adding stimulus to the sequencer'); @@ -512,7 +602,7 @@ void main() { group('divider tests', () { test('VF tests', () async { // Set the logger level - Logger.root.level = Level.OFF; + Logger.root.level = Level.SEVERE; // Create the testbench final intf = MultiCycleDividerInterface();