Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPI multiple subs functionality. #189

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions lib/src/gaskets/spi/spi_main.dart
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ class SpiMain extends Module {
required Logic reset,
required Logic start,
required Logic busIn,
Logic? css,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add documentation for what css is

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');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean to call this "active"?


// 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 <=
27 changes: 17 additions & 10 deletions lib/src/gaskets/spi/spi_sub.dart
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@ class SpiSub extends Module {
{required SpiInterface intf,
Logic? busIn,
Logic? reset,
bool triStateOutput = false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

document triStateOutput

super.name = 'spiSub'}) {
// SPI Interface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add validation that the interface always has 1 CS?

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: why not keep it as a flop like it was?

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;
}
}
}
15 changes: 10 additions & 5 deletions lib/src/interfaces/spi.dart
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the SPI checker need updating as well?

Original file line number Diff line number Diff line change
@@ -14,26 +14,31 @@ 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why no numChipSelects or something?


/// Serial clock (SCLK). Clock signal from main to sub(s).
Logic get sclk => port('SCLK');

/// Main Out Sub In (MOSI). Serial data from main to sub(s).
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<Logic> get csb => List.generate(multiChipSelects, (i) => port('CSB$i'));

/// Creates a new [SpiInterface].
SpiInterface({this.dataLength = 1})
SpiInterface({this.dataLength = 1, this.multiChipSelects = 1})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validation: do you want to check that these inputs are valid? both of these should be >= 1 right?

: 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();
}
6 changes: 3 additions & 3 deletions lib/src/models/spi_bfm/spi_main_driver.dart
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ class SpiMainDriver extends PendingClockedDriver<SpiPacket> {
unawaited(super.run(phase));

Simulator.injectAction(() {
intf.csb.put(1);
intf.csb[0].put(1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is incomplete on the BFM side, right? we need ways to drive different CSB's, not just always 0

intf.mosi.put(0);
});

@@ -48,7 +48,7 @@ class SpiMainDriver extends PendingClockedDriver<SpiPacket> {
} 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<SpiPacket> {

/// Drives a packet onto the interface.
Future<void> _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++) {
2 changes: 1 addition & 1 deletion lib/src/models/spi_bfm/spi_sub_driver.dart
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add validation that the interface always has 1 CS?

Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ class SpiSubDriver extends PendingDriver<SpiPacket> {

intf.miso.inject(0);

intf.csb.negedge.listen((_) {
intf.csb[0].negedge.listen((_) {
_packetHandler(loadOnly: true);
});

271 changes: 268 additions & 3 deletions test/spi/spi_gaskets_test.dart
Original file line number Diff line number Diff line change
@@ -243,6 +243,167 @@ class SpiPairTest extends Test {
}
}

class SpiMultiSubTest extends Test {
late final SpiInterface intfMain;
late final SpiInterface intfSubA;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: might be better to make this a List<SpiInterface> to reduce the copy-paste for A,B,C,D? And List<SpiSub> etc.

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<void> 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<void> 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<void> 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<void> 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;