-
Notifications
You must be signed in to change notification settings - Fork 28
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, | ||
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'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 <= | ||
|
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. document triStateOutput |
||
super.name = 'spiSub'}) { | ||
// SPI Interface | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: why not keep it as a |
||
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; | ||
} | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why no |
||
|
||
/// 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}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
: 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(); | ||
} |
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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++) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
---|---|---|
|
@@ -243,6 +243,167 @@ class SpiPairTest extends Test { | |
} | ||
} | ||
|
||
class SpiMultiSubTest extends Test { | ||
late final SpiInterface intfMain; | ||
late final SpiInterface intfSubA; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: might be better to make this a |
||
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; | ||
|
There was a problem hiding this comment.
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