diff --git a/lib/src/gaskets/spi/spi_main.dart b/lib/src/gaskets/spi/spi_main.dart index 9eceb882c..f228f5f6d 100644 --- a/lib/src/gaskets/spi/spi_main.dart +++ b/lib/src/gaskets/spi/spi_main.dart @@ -33,6 +33,7 @@ class SpiMain extends Module { required Logic reset, required Logic start, required Logic busIn, + Logic? css, super.name = 'spiMain'}) { busIn = addInput('busIn', busIn, width: busIn.width); @@ -42,6 +43,10 @@ class SpiMain extends Module { start = addInput('start', start); + if (css != null) { + css = addInput('css', css, width: intf.multiChipSelects); + } + addOutput('busOut', width: busIn.width); addOutput('done'); @@ -50,11 +55,15 @@ class SpiMain extends Module { ..pairConnectIO(this, intf, PairRole.provider); final isRunning = Logic(name: 'isRunning'); + final active = Logic(name: 'enable'); + + // Active will be high when start is pulsed high or isRunning is high. + active <= start | isRunning; // Counter to track of the number of bits shifted out. final count = Counter.simple( clk: ~clk, - enable: start | isRunning, + enable: active, reset: reset, asyncReset: true, resetValue: busIn.width - 1, @@ -87,11 +96,21 @@ class SpiMain extends Module { // NOTE: dataStage0 corresponds to the last bit shifted in. busOut <= shiftReg.stages.rswizzle(); - // SCLK runs off clk when isRunning is true or start is pulsed high. - intf.sclk <= ~clk & (isRunning | start); - - // CS is active low. It will go low when isRunning or start is pulsed high. - intf.csb <= ~(isRunning | start); + // SCLK runs off clk when active. + intf.sclk <= ~clk & active; + + // CS is active low. It will go low when active for single sub. + // if multiple sub, css will be used to control each csb. + + if (css != null) { + for (var i = 0; i < intf.multiChipSelects; i++) { + intf.csb[i] <= ~(~css[i] & active); + } + } else { + for (var i = 0; i < intf.multiChipSelects; i++) { + intf.csb[i] <= ~active; + } + } // MOSI is connected shift register dataOut. intf.mosi <= diff --git a/lib/src/gaskets/spi/spi_sub.dart b/lib/src/gaskets/spi/spi_sub.dart index ef9b74d6e..83f40f352 100644 --- a/lib/src/gaskets/spi/spi_sub.dart +++ b/lib/src/gaskets/spi/spi_sub.dart @@ -34,6 +34,7 @@ class SpiSub extends Module { {required SpiInterface intf, Logic? busIn, Logic? reset, + bool triStateOutput = false, super.name = 'spiSub'}) { // SPI Interface intf = SpiInterface.clone(intf) @@ -50,10 +51,10 @@ class SpiSub extends Module { addOutput('done'); - // Counter to track of the number of bits shifted out. + // Counter to track the number of bits shifted out. final count = Counter.simple( clk: intf.sclk, - enable: ~intf.csb, + enable: ~intf.csb[0], reset: reset, asyncReset: true, resetValue: intf.dataLength - 1, @@ -70,7 +71,7 @@ class SpiSub extends Module { // NOTE: Reset values are set to busIn values. final shiftReg = ShiftRegister( intf.mosi, - enable: ~intf.csb, + enable: ~intf.csb[0], clk: intf.sclk, depth: intf.dataLength, reset: reset, @@ -82,12 +83,18 @@ class SpiSub extends Module { // NOTE: dataStage0 corresponds to the last bit shifted in. busOut <= shiftReg.stages.rswizzle(); - // MISO is connected to shift register dataOut. - intf.miso <= - flop(~intf.sclk, shiftReg.dataOut, - en: ~intf.csb, - reset: reset, - asyncReset: true, - resetValue: busIn?[-1]); + // flop data from shift register dataOut. + final flopDataOut = FlipFlop(~intf.sclk, shiftReg.dataOut, + en: ~intf.csb[0], + reset: reset, + asyncReset: true, + resetValue: busIn?[-1]); + + // Connect flopDataOut to MISO via tri-state buffer if enabled. + if (triStateOutput) { + intf.miso <= TriStateBuffer(flopDataOut.q, enable: ~intf.csb[0]).out; + } else { + intf.miso <= flopDataOut.q; + } } } diff --git a/lib/src/interfaces/spi.dart b/lib/src/interfaces/spi.dart index bc29c04ee..7c7642865 100644 --- a/lib/src/interfaces/spi.dart +++ b/lib/src/interfaces/spi.dart @@ -14,6 +14,9 @@ class SpiInterface extends PairInterface { /// The data length for serial transmissions on this interface. final int dataLength; + /// The number of Chip Select signals in this interface. + final int multiChipSelects; + /// Serial clock (SCLK). Clock signal from main to sub(s). Logic get sclk => port('SCLK'); @@ -21,19 +24,21 @@ class SpiInterface extends PairInterface { Logic get mosi => port('MOSI'); /// Main In Sub Out (MISO). Serial data from sub(s) to main. - Logic get miso => port('MISO'); + LogicNet get miso => port('MISO') as LogicNet; /// Chip select (active low). Chip select signal from main to sub. - Logic get csb => port('CSB'); + List get csb => List.generate(multiChipSelects, (i) => port('CSB$i')); /// Creates a new [SpiInterface]. - SpiInterface({this.dataLength = 1}) + SpiInterface({this.dataLength = 1, this.multiChipSelects = 1}) : super( - portsFromConsumer: [Port('MISO')], - portsFromProvider: [Port('MOSI'), Port('CSB'), Port('SCLK')]); + portsFromConsumer: [LogicNet.port('MISO')], + portsFromProvider: [Port('MOSI'), Port('SCLK')] + + List.generate(multiChipSelects, (i) => Port('CSB$i'))); /// Clones this [SpiInterface]. SpiInterface.clone(SpiInterface super.otherInterface) : dataLength = otherInterface.dataLength, + multiChipSelects = otherInterface.multiChipSelects, super.clone(); } diff --git a/lib/src/models/spi_bfm/spi_main_driver.dart b/lib/src/models/spi_bfm/spi_main_driver.dart index 92004a994..b0a30ee86 100644 --- a/lib/src/models/spi_bfm/spi_main_driver.dart +++ b/lib/src/models/spi_bfm/spi_main_driver.dart @@ -38,7 +38,7 @@ class SpiMainDriver extends PendingClockedDriver { unawaited(super.run(phase)); Simulator.injectAction(() { - intf.csb.put(1); + intf.csb[0].put(1); intf.mosi.put(0); }); @@ -48,7 +48,7 @@ class SpiMainDriver extends PendingClockedDriver { } else { await clk.nextNegedge; Simulator.injectAction(() { - intf.csb.put(1); + intf.csb[0].put(1); _clkenable.inject(0); intf.mosi.put(0); }); @@ -61,7 +61,7 @@ class SpiMainDriver extends PendingClockedDriver { /// Drives a packet onto the interface. Future _drivePacket(SpiPacket packet) async { - intf.csb.inject(0); + intf.csb[0].inject(0); // Loop through the bits of the packet for (var i = 1; i <= packet.data.width; i++) { diff --git a/lib/src/models/spi_bfm/spi_sub_driver.dart b/lib/src/models/spi_bfm/spi_sub_driver.dart index 2e03e6f3b..1f4c2971d 100644 --- a/lib/src/models/spi_bfm/spi_sub_driver.dart +++ b/lib/src/models/spi_bfm/spi_sub_driver.dart @@ -31,7 +31,7 @@ class SpiSubDriver extends PendingDriver { intf.miso.inject(0); - intf.csb.negedge.listen((_) { + intf.csb[0].negedge.listen((_) { _packetHandler(loadOnly: true); }); diff --git a/test/spi/spi_gaskets_test.dart b/test/spi/spi_gaskets_test.dart index d78cfe2aa..56f8c5eff 100644 --- a/test/spi/spi_gaskets_test.dart +++ b/test/spi/spi_gaskets_test.dart @@ -243,6 +243,167 @@ class SpiPairTest extends Test { } } +class SpiMultiSubTest extends Test { + late final SpiInterface intfMain; + late final SpiInterface intfSubA; + late final SpiInterface intfSubB; + late final SpiInterface intfSubC; + late final SpiInterface intfSubD; + late final SpiMonitor monitor; + late final SpiMain main; + late final SpiSub subA; + late final SpiSub subB; + late final SpiSub subC; + late final SpiSub subD; + late final Logic clk; + late final Logic resetMain; + late final Logic busInMain; + late final Logic resetSubA; + late final Logic resetSubB; + late final Logic resetSubC; + late final Logic resetSubD; + late final Logic resetSubAll; + late final Logic busInSubA; + late final Logic busInSubB; + late final Logic busInSubC; + late final Logic busInSubD; + late final Logic css; + + late final Logic starts; + + String get outFolder => 'tmp_test/spiMultiSub/$name/'; + + final Future Function(SpiMultiSubTest test) stimulus; + + SpiMultiSubTest(this.stimulus, super.name) : super() { + intfMain = SpiInterface(dataLength: 8, multiChipSelects: 4); + intfSubA = SpiInterface(dataLength: intfMain.dataLength); + intfSubB = SpiInterface(dataLength: intfMain.dataLength); + intfSubC = SpiInterface(dataLength: intfMain.dataLength); + intfSubD = SpiInterface(dataLength: intfMain.dataLength); + + monitor = SpiMonitor(intf: intfMain, parent: this); + + Directory(outFolder).createSync(recursive: true); + + final tracker = + SpiTracker(intf: intfMain, dumpTable: false, outputFolder: outFolder); + + SpiChecker(intfMain, parent: this); + + clk = SimpleClockGenerator(10).clk; + + // initialize main + resetMain = Logic(); + busInMain = Logic(width: intfMain.dataLength); + starts = Logic(); + css = Logic(width: intfMain.multiChipSelects); + + main = SpiMain(intfMain, + busIn: busInMain, clk: clk, reset: resetMain, start: starts, css: css); + + //init sub + resetSubA = Logic(); + resetSubB = Logic(); + resetSubC = Logic(); + resetSubD = Logic(); + resetSubAll = Logic(); + + resetSubA <= resetSubAll; + resetSubB <= resetSubAll; + resetSubC <= resetSubAll; + resetSubD <= resetSubAll; + + busInSubA = Logic(width: intfMain.dataLength); + busInSubB = Logic(width: intfMain.dataLength); + busInSubC = Logic(width: intfMain.dataLength); + busInSubD = Logic(width: intfMain.dataLength); + + subA = SpiSub( + intf: intfSubA, + busIn: busInSubA, + reset: resetSubA, + triStateOutput: true); + subB = SpiSub( + intf: intfSubB, + busIn: busInSubB, + reset: resetSubB, + triStateOutput: true); + subC = SpiSub( + intf: intfSubC, + busIn: busInSubC, + reset: resetSubC, + triStateOutput: true); + subD = SpiSub( + intf: intfSubD, + busIn: busInSubD, + reset: resetSubD, + triStateOutput: true); + + //connect interfaces + intfSubA.sclk <= intfMain.sclk; + intfSubB.sclk <= intfMain.sclk; + intfSubC.sclk <= intfMain.sclk; + intfSubD.sclk <= intfMain.sclk; + + intfSubA.mosi <= intfMain.mosi; + intfSubB.mosi <= intfMain.mosi; + intfSubC.mosi <= intfMain.mosi; + intfSubD.mosi <= intfMain.mosi; + + intfSubA.miso <= intfMain.miso; + intfSubB.miso <= intfMain.miso; + intfSubC.miso <= intfMain.miso; + intfSubD.miso <= intfMain.miso; + + intfSubA.csb[0] <= intfMain.csb[0]; + intfSubB.csb[0] <= intfMain.csb[1]; + intfSubC.csb[0] <= intfMain.csb[2]; + intfSubD.csb[0] <= intfMain.csb[3]; + + Simulator.registerEndOfSimulationAction(() async { + await tracker.terminate(); + Directory(outFolder).deleteSync(recursive: true); + }); + + monitor.stream.listen(tracker.record); + } + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + final obj = phase.raiseObjection('SpiPairTestObj'); + + // Initialize all inputs to initial state. + // Just for waveform clarity. + await clk.waitCycles(1); + + busInMain.inject(00); + busInSubA.inject(00); + busInSubB.inject(00); + busInSubC.inject(00); + busInSubD.inject(00); + css.inject(0xF); // 1111 all cs are active low + starts.inject(false); + resetMain.inject(false); + resetSubAll.inject(false); + + await clk.waitCycles(1); + resetMain.inject(true); + resetSubAll.inject(true); + + await clk.waitCycles(1); + resetMain.inject(false); + resetSubAll.inject(false); + + await clk.waitCycles(1); + await stimulus(this); + + obj.drop(); + } +} + class SpiCheckerTest extends Test { late final SpiInterface intf; late final Logic clk; @@ -253,7 +414,7 @@ class SpiCheckerTest extends Test { SpiChecker(intf, parent: this); - intf.csb <= Const(0); + intf.csb[0] <= Const(0); intf.sclk <= clk; intf.mosi <= ~clk; intf.miso <= clk; @@ -653,8 +814,6 @@ void main() { test('main and sub busIn, both busOut checks', () async { await runPairTest(SpiPairTest((test) async { // Send regular main data. - // var mainData = Random().nextInt(256); - // var subData = Random().nextInt(256); await sendBothData(test, mainData: 0x73, subData: 0x00); // 0111 0011 checkSubBusOut(test, 0x73); checkMainBusOut(test, 0x00); @@ -683,6 +842,112 @@ void main() { }); }); + group('multi sub tests', () { + Future runMultiSubTest(SpiMultiSubTest spiMultiSubTest, + {bool dumpWaves = false}) async { + Simulator.setMaxSimTime(3000); + final mod = SpiTop(spiMultiSubTest.intfMain, null); + if (dumpWaves) { + await mod.build(); + WaveDumper(mod, outputPath: '${spiMultiSubTest.outFolder}/waves.vcd'); + } + await spiMultiSubTest.start(); + } + + Future sendMainData(SpiMultiSubTest test, int data, int css) async { + test.busInMain.inject(LogicValue.ofInt(data, test.intfMain.dataLength)); + test.css.inject(LogicValue.ofInt(css, test.intfMain.multiChipSelects)); + await test.clk.nextNegedge; + test.resetMain.inject(true); + await test.clk.nextPosedge; + test.resetMain.inject(false); + test.starts.inject(true); + await test.clk.waitCycles(1); + test.starts.inject(false); + await test.clk.waitCycles(7); + } + + void checkMainBusOut(SpiMultiSubTest test, int data) { + if (test.main.busOut.value.toInt() != data) { + test.logger.severe('main busOut: ${test.main.busOut.value}'); + } + } + + void checkSubBusOut(SpiMultiSubTest test, int data, int subX) { + final subMap = { + 0: test.subA, + 1: test.subB, + 2: test.subC, + 3: test.subD, + }; + final sub = subMap[subX]; + if (sub != null && sub.busOut.value.toInt() != data) { + test.logger.severe('sub busOut: ${sub.busOut.value}'); + } + } + + test('Main injects, all busOut checks', () async { + await runMultiSubTest(SpiMultiSubTest((test) async { + // Send new main data to sub0 + await sendMainData(test, 0x73, 0xE); + // Main busOut should read 00 after writing to all subs first time. + checkMainBusOut(test, 0); + await test.clk.waitCycles(2); + // Send new main data to sub1. + await sendMainData(test, 0xCD, 0xD); + checkMainBusOut(test, 0); + await test.clk.waitCycles(2); + // Send new main data to sub2. + await sendMainData(test, 0xE2, 0xB); + checkMainBusOut(test, 0); + await test.clk.waitCycles(2); + // Send new main data to sub3. + await sendMainData(test, 0xB3, 0x7); + checkMainBusOut(test, 0); + await test.clk.waitCycles(2); + // Check all subs + checkSubBusOut(test, 0x73, 0); + checkSubBusOut(test, 0xCD, 1); + checkSubBusOut(test, 0xE2, 2); + checkSubBusOut(test, 0xB3, 3); + + // Send new main data to sub0 and 1 at the same time 0b1100 + await sendMainData(test, 0x4C, 0xC); + // main busOut should be 'x' due to multi subs writing at the same time + expect(test.main.busOut.value.isValid, false); + await test.clk.waitCycles(2); + + // Send new main data to sub2 and 3 at the same time 0b0011 + await sendMainData(test, 0x27, 0x3); + expect(test.main.busOut.value.isValid, false); + await test.clk.waitCycles(2); + + // Check all + checkSubBusOut(test, 0x4C, 0); + checkSubBusOut(test, 0x4C, 1); + checkSubBusOut(test, 0x27, 2); + checkSubBusOut(test, 0x27, 3); + + await test.clk.waitCycles(2); + + // repeat first no gaps. + await sendMainData(test, 0x14, 0xE); + + await sendMainData(test, 0x6C, 0xD); + + await sendMainData(test, 0x93, 0xB); + + await sendMainData(test, 0xDF, 0x7); + // Check all + checkSubBusOut(test, 0x14, 0); + checkSubBusOut(test, 0x6C, 1); + checkSubBusOut(test, 0x93, 2); + checkSubBusOut(test, 0xDF, 3); + + await test.clk.waitCycles(2); + }, 'testMultiSub')); + }); + }); test('SpiChecker test', () async { final checkerTest = SpiCheckerTest('checkerTest'); var sawError = false;