11using System ;
22using System . ComponentModel ;
3- using System . Linq ;
4- using System . Reactive . Disposables ;
5- using System . Reactive . Subjects ;
63using Bonsai ;
74
85namespace OpenEphys . Onix1
96{
10- // TODO: Add data IO operators, update XML comment to link to them (<see cref="LoadTesterData"/>)
117 /// <summary>
12- /// Configures a load tester device.
8+ /// Configures a load tester device for measuring system performance .
139 /// </summary>
1410 /// <remarks>
15- /// This configuration operator can be linked to a data IO operator, such as LoadTesterData,
16- /// using a shared <c>DeviceName</c>. The load tester device can be configured
17- /// to produce data at user-settable size and rate to stress test various communication links and test
18- /// closed-loop response latency.
11+ /// This configuration operator can be linked to a data IO operator, such as <see cref=" LoadTesterData"/> ,
12+ /// using a shared <c>DeviceName</c>. The load tester device can be configured to produce and consume data
13+ /// at user-defined sizes and rates to stress test various communication links and measure closed-loop
14+ /// response latency using a high-resolution hardware timer .
1915 /// </remarks>
2016 [ Description ( "Configures a load testing device." ) ]
2117 public class ConfigureLoadTester : SingleDeviceFactory
2218 {
23- readonly BehaviorSubject < uint > frameHz = new ( 1000 ) ;
24-
2519 /// <summary>
2620 /// Initializes a new instance of the <see cref="ConfigureLoadTester"/> class.
2721 /// </summary>
@@ -38,31 +32,37 @@ public ConfigureLoadTester()
3832 public bool Enable { get ; set ; } = false ;
3933
4034 /// <summary>
41- /// Gets or sets the number of repetitions of the 16-bit unsigned integer 42 sent with each read- frame.
35+ /// Gets or sets the number of repetitions incrementing, unsigned 16-bit integers sent with each read frame.
4236 /// </summary>
37+ /// <remarks>
38+ /// These data are produced by the controller and are used to impose a load the controller to host
39+ /// communication. These data can be used in downstream computational operations that model the
40+ /// computational load imposed by a closed-loop algorithm.
41+ /// </remarks>
4342 [ Category ( ConfigurationCategory ) ]
4443 [ Description ( "Number of repetitions of the 16-bit unsigned integer 42 sent with each read-frame." ) ]
4544 [ Range ( 0 , 10e6 ) ]
4645 public uint ReceivedWords { get ; set ; }
4746
4847 /// <summary>
49- /// Gets or sets the number of repetitions of the 32-bit integer 42 sent with each write frame.
48+ /// Gets or sets the number of repetitions of the 32-bit integer dummy words sent with each write
49+ /// frame.
5050 /// </summary>
51+ /// <remarks>
52+ /// These data are produced by the host and are used to impose a load on host to controller
53+ /// commutation. They are discarded by the controller when they are received.
54+ /// </remarks>
5155 [ Category ( ConfigurationCategory ) ]
5256 [ Description ( "Number of repetitions of the 32-bit integer 42 sent with each write frame." ) ]
5357 [ Range ( 0 , 10e6 ) ]
5458 public uint TransmittedWords { get ; set ; }
5559
5660 /// <summary>
57- /// Gets or sets a value specifying the rate at which frames are produced, in Hz.
61+ /// Gets or sets a value specifying the rate at which frames are produced in Hz.
5862 /// </summary>
59- [ Category ( AcquisitionCategory ) ]
63+ [ Category ( ConfigurationCategory ) ]
6064 [ Description ( "Specifies the rate at which frames are produced (Hz)." ) ]
61- public uint FramesPerSecond
62- {
63- get { return frameHz . Value ; }
64- set { frameHz . OnNext ( value ) ; }
65- }
65+ public uint FramesPerSecond { get ; set ; }
6666
6767 /// <summary>
6868 /// Configures a load testing device.
@@ -83,40 +83,38 @@ public override IObservable<ContextTask> Process(IObservable<ContextTask> source
8383 var deviceAddress = DeviceAddress ;
8484 var receivedWords = ReceivedWords ;
8585 var transmittedWords = TransmittedWords ;
86+ var framesPerSecond = FramesPerSecond ;
87+
8688 return source . ConfigureDevice ( ( context , observer ) =>
8789 {
8890 var device = context . GetDeviceContext ( deviceAddress , DeviceType ) ;
8991 device . WriteRegister ( LoadTester . ENABLE , enable ? 1u : 0u ) ;
9092
9193 var clockHz = device . ReadRegister ( LoadTester . CLK_HZ ) ;
9294
93- // Assumes 8-byte timer
94- uint ValidSize ( )
95+ const int OverheadCycles = 9 ; // 4 cycles to produce hub clock, and 5 state machine overhead per the datasheet
96+
97+ var maxFramesPerSecond = clockHz / OverheadCycles ;
98+ if ( framesPerSecond > maxFramesPerSecond )
9599 {
96- var clkDiv = device . ReadRegister ( LoadTester . CLK_DIV ) ;
97- return clkDiv - 4 - 10 ; // -10 is overhead hack
100+ throw new ArgumentOutOfRangeException ( nameof ( FramesPerSecond ) , $ "{ nameof ( FramesPerSecond ) } must be less than { maxFramesPerSecond } .") ;
98101 }
99102
100- var maxSize = ValidSize ( ) ;
101- var bounded = receivedWords > maxSize ? maxSize : receivedWords ;
102- device . WriteRegister ( LoadTester . DT0H16_WORDS , bounded ) ;
103+ device . WriteRegister ( LoadTester . CLK_DIV , clockHz / framesPerSecond ) ;
103104
104- var writeArray = Enumerable . Repeat ( ( uint ) 42 , ( int ) ( transmittedWords + 2 ) ) . ToArray ( ) ;
105- device . WriteRegister ( LoadTester . HTOD32_WORDS , transmittedWords ) ;
106- var frameHzSubscription = frameHz . SubscribeSafe ( observer , newValue =>
105+ var maxSize = device . ReadRegister ( LoadTester . CLK_DIV ) - OverheadCycles ;
106+
107+ if ( receivedWords > maxSize )
107108 {
108- device . WriteRegister ( LoadTester . CLK_DIV , clockHz / newValue ) ;
109- var maxSize = ValidSize ( ) ;
110- if ( receivedWords > maxSize )
111- {
112- receivedWords = maxSize ;
113- }
114- } ) ;
115-
116- return new CompositeDisposable (
117- DeviceManager . RegisterDevice ( deviceName , device , DeviceType ) ,
118- frameHzSubscription
119- ) ;
109+ throw new ArgumentOutOfRangeException ( nameof ( ReceivedWords ) ,
110+ $ "{ nameof ( ReceivedWords ) } must be less than { maxSize } for the requested frame rate of { framesPerSecond } Hz.") ;
111+ }
112+
113+ device . WriteRegister ( LoadTester . DT0H16_WORDS , receivedWords ) ;
114+ device . WriteRegister ( LoadTester . HTOD32_WORDS , transmittedWords ) ;
115+
116+ var deviceInfo = new LoadTesterDeviceInfo ( context , DeviceType , deviceAddress , ReceivedWords , TransmittedWords ) ;
117+ return DeviceManager . RegisterDevice ( deviceName , deviceInfo ) ;
120118 } ) ;
121119 }
122120 }
@@ -132,12 +130,13 @@ static class LoadTester
132130 public const uint DT0H16_WORDS = 3 ; // Number of repetitions of 16-bit unsigned integer 42 sent with each frame.
133131 // Note: max here depends of CLK_HZ and CLK_DIV. There needs to be enough clock
134132 // cycles to push the data at the requested CLK_HZ. Specifically,
135- // CLK_HZ / CLK_DIV >= TX16_WORDS + 9. Going above this will result in
133+ // CLK_HZ / CLK_DIV >= DT0H16_WORDS + 9. Going above this will result in
136134 // decreased bandwidth as samples will be skipped.
137135 public const uint HTOD32_WORDS = 4 ; // Number of 32-bit words in a write-frame. All write frame data is ignored except
138136 // the first 64-bits, which are looped back into the device to host data frame for
139137 // testing loop latency. This value must be at least 2.
140138
139+
141140 internal class NameConverter : DeviceNameConverter
142141 {
143142 public NameConverter ( )
0 commit comments