origen_spi 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/config/commands.rb CHANGED
@@ -1,87 +1,87 @@
1
- # This file should be used to extend the origen with application specific commands
2
-
3
- # Map any command aliases here, for example to allow 'origen ex' to refer to a
4
- # command called execute you would add a reference as shown below:
5
- aliases ={
6
- # "ex" => "execute",
7
- }
8
-
9
- # The requested command is passed in here as @command, this checks it against
10
- # the above alias table and should not be removed.
11
- @command = aliases[@command] || @command
12
-
13
- # Now branch to the specific task code
14
- case @command
15
-
16
- # (Working) example of how to create an application specific comment, here to generate
17
- # a tags file for you application to enable method definition lookup and similar within
18
- # editors/IDEs
19
- when "tags"
20
- # Here the logic is just written in-line, alternatively it could be written in a
21
- # dedicated file and required here, e.g.
22
- #require "origen_spi/commands/my_command" # Would load file lib/origen_spi/commands/my_command.rb
23
- Dir.chdir Origen.root do
24
- system("ripper-tags -R")
25
- end
26
- # You must always exit upon successfully capturing and executing a command to prevent
27
- # control flowing back to Origen
28
- exit 0
29
-
30
- ## Example of how to make a command to run unit tests, this simply invokes RSpec on
31
- ## the spec directory
32
- when "specs"
33
- require "rspec"
34
- exit RSpec::Core::Runner.run(['spec'])
35
-
36
- ## Example of how to make a command to run diff-based tests
37
- when "examples", "test"
38
- Origen.load_application
39
- status = 0
40
-
41
- ARGV = %w(pattern/clock_test.rb -t default.rb -e default.rb -r approved)
42
- load "#{Origen.top}/lib/origen/commands/generate.rb"
43
-
44
- ARGV = %w(pattern/shift_test.rb -t default.rb -e default.rb -r approved)
45
- load "#{Origen.top}/lib/origen/commands/generate.rb"
46
-
47
- ARGV = %w(pattern/overlay_test.rb -t default.rb -e default.rb -r approved)
48
- load "#{Origen.top}/lib/origen/commands/generate.rb"
49
-
50
- ARGV = %w(pattern/keep_ss_active.rb -t default.rb -e default.rb -r approved)
51
- load "#{Origen.top}/lib/origen/commands/generate.rb"
52
-
53
- ARGV = %w(pattern/clear_flags_test.rb -t default.rb -e default.rb -r approved)
54
- load "#{Origen.top}/lib/origen/commands/generate.rb"
55
-
56
- if Origen.app.stats.changed_files == 0 &&
57
- Origen.app.stats.new_files == 0 &&
58
- Origen.app.stats.changed_patterns == 0 &&
59
- Origen.app.stats.new_patterns == 0
60
-
61
- Origen.app.stats.report_pass
62
- else
63
- Origen.app.stats.report_fail
64
- status = 1
65
- end
66
- puts
67
- if @command == "test"
68
- Origen.app.unload_target!
69
- require "rspec"
70
- result = RSpec::Core::Runner.run(['spec'])
71
- status = status == 1 ? 1 : result
72
- end
73
- exit status # Exit with a 1 on the event of a failure per std unix result codes
74
-
75
- # Always leave an else clause to allow control to fall back through to the
76
- # Origen command handler.
77
- else
78
- # You probably want to also add the your commands to the help shown via
79
- # origen -h, you can do this by assigning the required text to @application_commands
80
- # before handing control back to Origen.
81
- @application_commands = <<-EOT
82
- tags Build a tags file for this app
83
- EOT
84
- # specs Run the specs (tests), -c will enable coverage
85
- # examples Run the examples (tests), -c will enable coverage
86
- # test Run both specs and examples, -c will enable coverage
87
- end
1
+ # This file should be used to extend the origen with application specific commands
2
+
3
+ # Map any command aliases here, for example to allow 'origen ex' to refer to a
4
+ # command called execute you would add a reference as shown below:
5
+ aliases ={
6
+ # "ex" => "execute",
7
+ }
8
+
9
+ # The requested command is passed in here as @command, this checks it against
10
+ # the above alias table and should not be removed.
11
+ @command = aliases[@command] || @command
12
+
13
+ # Now branch to the specific task code
14
+ case @command
15
+
16
+ # (Working) example of how to create an application specific comment, here to generate
17
+ # a tags file for you application to enable method definition lookup and similar within
18
+ # editors/IDEs
19
+ when "tags"
20
+ # Here the logic is just written in-line, alternatively it could be written in a
21
+ # dedicated file and required here, e.g.
22
+ #require "origen_spi/commands/my_command" # Would load file lib/origen_spi/commands/my_command.rb
23
+ Dir.chdir Origen.root do
24
+ system("ripper-tags -R")
25
+ end
26
+ # You must always exit upon successfully capturing and executing a command to prevent
27
+ # control flowing back to Origen
28
+ exit 0
29
+
30
+ ## Example of how to make a command to run unit tests, this simply invokes RSpec on
31
+ ## the spec directory
32
+ when "specs"
33
+ require "rspec"
34
+ exit RSpec::Core::Runner.run(['spec'])
35
+
36
+ ## Example of how to make a command to run diff-based tests
37
+ when "examples", "test"
38
+ Origen.load_application
39
+ status = 0
40
+
41
+ ARGV = %w(pattern/clock_test.rb -t default.rb -e default.rb -r approved)
42
+ load "#{Origen.top}/lib/origen/commands/generate.rb"
43
+
44
+ ARGV = %w(pattern/shift_test.rb -t default.rb -e default.rb -r approved)
45
+ load "#{Origen.top}/lib/origen/commands/generate.rb"
46
+
47
+ ARGV = %w(pattern/overlay_test.rb -t default.rb -e default.rb -r approved)
48
+ load "#{Origen.top}/lib/origen/commands/generate.rb"
49
+
50
+ ARGV = %w(pattern/keep_ss_active.rb -t default.rb -e default.rb -r approved)
51
+ load "#{Origen.top}/lib/origen/commands/generate.rb"
52
+
53
+ ARGV = %w(pattern/clear_flags_test.rb -t default.rb -e default.rb -r approved)
54
+ load "#{Origen.top}/lib/origen/commands/generate.rb"
55
+
56
+ if Origen.app.stats.changed_files == 0 &&
57
+ Origen.app.stats.new_files == 0 &&
58
+ Origen.app.stats.changed_patterns == 0 &&
59
+ Origen.app.stats.new_patterns == 0
60
+
61
+ Origen.app.stats.report_pass
62
+ else
63
+ Origen.app.stats.report_fail
64
+ status = 1
65
+ end
66
+ puts
67
+ if @command == "test"
68
+ Origen.app.unload_target!
69
+ require "rspec"
70
+ result = RSpec::Core::Runner.run(['spec'])
71
+ status = status == 1 ? 1 : result
72
+ end
73
+ exit status # Exit with a 1 on the event of a failure per std unix result codes
74
+
75
+ # Always leave an else clause to allow control to fall back through to the
76
+ # Origen command handler.
77
+ else
78
+ # You probably want to also add the your commands to the help shown via
79
+ # origen -h, you can do this by assigning the required text to @application_commands
80
+ # before handing control back to Origen.
81
+ @application_commands = <<-EOT
82
+ tags Build a tags file for this app
83
+ EOT
84
+ # specs Run the specs (tests), -c will enable coverage
85
+ # examples Run the examples (tests), -c will enable coverage
86
+ # test Run both specs and examples, -c will enable coverage
87
+ end
data/config/version.rb CHANGED
@@ -1,8 +1,8 @@
1
- module OrigenSpi
2
- MAJOR = 0
3
- MINOR = 1
4
- BUGFIX = 1
5
- DEV = nil
6
-
7
- VERSION = [MAJOR, MINOR, BUGFIX].join(".") + (DEV ? ".pre#{DEV}" : '')
8
- end
1
+ module OrigenSpi
2
+ MAJOR = 0
3
+ MINOR = 2
4
+ BUGFIX = 0
5
+ DEV = nil
6
+
7
+ VERSION = [MAJOR, MINOR, BUGFIX].join(".") + (DEV ? ".pre#{DEV}" : '')
8
+ end
data/lib/origen_spi.rb CHANGED
@@ -1,19 +1,19 @@
1
- require 'origen'
2
- require 'origen_testers'
3
- require_relative '../config/application.rb'
4
- module OrigenSpi
5
- # THIS FILE SHOULD ONLY BE USED TO LOAD RUNTIME DEPENDENCIES
6
- # If this plugin has any development dependencies (e.g. dummy DUT or other models that are only used
7
- # for testing), then these should be loaded from config/boot.rb
8
-
9
- # Example of how to explicitly require a file
10
- # require "origen_spi/my_file"
11
-
12
- # Load all files in the lib/origen_spi directory.
13
- # Note that there is no problem from requiring a file twice (Ruby will ignore
14
- # the second require), so if you have a file that must be required first, then
15
- # explicitly require it up above and then let this take care of the rest.
16
- Dir.glob("#{File.dirname(__FILE__)}/origen_spi/**/*.rb").sort.each do |file|
17
- require file
18
- end
19
- end
1
+ require 'origen'
2
+ require 'origen_testers'
3
+ require_relative '../config/application.rb'
4
+ module OrigenSpi
5
+ # THIS FILE SHOULD ONLY BE USED TO LOAD RUNTIME DEPENDENCIES
6
+ # If this plugin has any development dependencies (e.g. dummy DUT or other models that are only used
7
+ # for testing), then these should be loaded from config/boot.rb
8
+
9
+ # Example of how to explicitly require a file
10
+ # require "origen_spi/my_file"
11
+
12
+ # Load all files in the lib/origen_spi directory.
13
+ # Note that there is no problem from requiring a file twice (Ruby will ignore
14
+ # the second require), so if you have a file that must be required first, then
15
+ # explicitly require it up above and then let this take care of the rest.
16
+ Dir.glob("#{File.dirname(__FILE__)}/origen_spi/**/*.rb").sort.each do |file|
17
+ require file
18
+ end
19
+ end
@@ -1,367 +1,374 @@
1
- module OrigenSpi
2
- class Driver
3
- include Origen::Model
4
-
5
- # Dut pin that serves as the spi clock
6
- # @example
7
- # spi_instance.sclk_pin = dut.pin(:p1)
8
- attr_reader :sclk_pin
9
-
10
- # Dut pin that serves as the master out slave in pin
11
- # @example
12
- # spi_instance.mosi_pin = dut.pin(:p2)
13
- attr_reader :mosi_pin
14
-
15
- # Dut pin that serves as the master in slave out
16
- # @example
17
- # spi_instance.miso_pin = dut.pin(:p3)
18
- attr_reader :miso_pin
19
-
20
- # Dut pin that serves as the slave select
21
- # @example
22
- # spi_instance.ss_pin = dut.pin(:p4)
23
- # @example
24
- # spi_instance.ss_pin = nil # no ss pin
25
- attr_reader :ss_pin
26
-
27
- # clock format
28
- #
29
- # available options are:
30
- # :rl # return low - data changes while sclk is low, latches on rising edge
31
- # :rh # return high
32
- #
33
- # @example
34
- # spi_instance.clk_format = :rl
35
- attr_reader :clk_format
36
-
37
- # pin state corresponding to slave select active
38
- # @example
39
- # spi_instance.ss_active = 0
40
- attr_reader :ss_active
41
-
42
- # time between ss_active and the first spi clock
43
- #
44
- # hash containing the wait time
45
- #
46
- # @example
47
- # spi_instance.clk_wait_time = {time_in_us: 0} # no delay
48
- # spi_instance.clk_wait_time = {time_in_ms: 1} # 1ms delay
49
- # spi_instance.clk_wait_time = {time_in_cycles: 2} # 2 cycle delay
50
- attr_reader :clk_wait_time
51
-
52
- # number of tester cycles per spi clock
53
- attr_reader :clk_multiple
54
-
55
- # cycle on which miso compares are placed (0 is the first cycle)
56
- attr_reader :miso_compare_cycle
57
-
58
- # data order
59
- #
60
- # available options are:
61
- # :msb0 - MSB is shifted first
62
- # :lsb0
63
- #
64
- # @example
65
- # spi_instance.data_order = :lsb0
66
- attr_reader :data_order
67
-
68
- # internal attribute
69
- attr_reader :settings_validated
70
-
71
- # options hash can configure
72
- #
73
- # @example
74
- # options = {
75
- # sclk_pin: dut.pin(:p1),
76
- # mosi_pin: dut.pin(:p2),
77
- # miso_pin: dut.pin(:p3),
78
- # ss_pin: dut.pin(:p4),
79
- # clk_format: :rl,
80
- # ss_active: 0,
81
- # clk_wait_time: {time_in_cycles: 2},
82
- # clk_multiple: 2,
83
- # miso_compare_cycle: 1,
84
- # data_order: :msb0
85
- # }
86
- # spi = OrigenSpi::Driver.new(options)
87
- def initialize(options = {})
88
- options = {
89
- sclk_pin: nil,
90
- mosi_pin: nil,
91
- miso_pin: nil,
92
- ss_pin: nil,
93
- clk_format: :nr,
94
- ss_active: 0,
95
- clk_wait_time: { time_in_us: 0 },
96
- clk_multiple: 2,
97
- data_order: :msb0
98
- }.merge(options)
99
-
100
- @sclk_pin = options[:sclk_pin]
101
- @mosi_pin = options[:mosi_pin]
102
- @miso_pin = options[:miso_pin]
103
- @ss_pin = options[:ss_pin]
104
- @clk_format = options[:clk_format]
105
- @ss_active = options[:ss_active]
106
- @clk_wait_time = options[:clk_wait_time]
107
- @clk_multiple = options[:clk_multiple]
108
- @miso_compare_cycle = options[:miso_compare_cycle] ? options[:miso_compare_cycle] : @clk_multiple - 1
109
- @data_order = options[:data_order]
110
- @settings_validated = false
111
- end
112
-
113
- def sclk_pin=(pin)
114
- @sclk_pin = pin
115
- @settings_validated = false
116
- end
117
-
118
- def mosi_pin=(pin)
119
- @mosi_pin = pin
120
- @settings_validated = false
121
- end
122
-
123
- def miso_pin=(pin)
124
- @miso_pin = pin
125
- @settings_validated = false
126
- end
127
-
128
- def ss_pin=(pin)
129
- @ss_pin = pin
130
- @settings_validated = false
131
- end
132
-
133
- def clk_format=(format)
134
- if format == :nr || format == :rl || format == :rh
135
- @clk_format = format
136
- else
137
- Origen.log.error "Invalid clock format for OrigenSpi::Driver -> #{format}"
138
- Origen.log.error 'Valid formats are :rl, :rh'
139
- end
140
- @settings_validated = false
141
- end
142
-
143
- def ss_active=(state)
144
- if state == 0 || state == 1
145
- @ss_active = state
146
- else
147
- Origen.log.error 'Invalid OrigenSpi::Driver.ss_active state (valid states are 0 and 1)'
148
- end
149
- @settings_validated = false
150
- end
151
-
152
- def clk_wait_time=(clk_wait)
153
- @clk_wait_time = clk_wait
154
- @settings_validated = false
155
- end
156
-
157
- def clk_multiple=(mult)
158
- @clk_multiple = mult
159
- @settings_validated = false
160
- end
161
-
162
- def miso_compare_cycle=(cycle)
163
- @miso_compare_cycle = cycle
164
- @settings_validated = false
165
- end
166
-
167
- def data_order=(order)
168
- if order == :msb0 || order == :lsb0
169
- @data_order = order
170
- else
171
- Origen.log.error "Invalid OrigenSpi::Driver.data_order -> #{order}, (use :msb0 or :lsb0)"
172
- end
173
- end
174
-
175
- # Check settings
176
- def validate_settings
177
- unless @settings_validated
178
- settings_valid = true
179
-
180
- # check that clock and miso are provided
181
- unless @sclk_pin.is_a?(Origen::Pins::Pin)
182
- settings_valid = false
183
- Origen.log.error 'OrigenSpi::Driver.sclk_pin must be an Origen pin object'
184
- end
185
-
186
- unless @clk_format == :rl || @clk_format == :rh
187
- settings_valid = false
188
- Origen.log.error 'OrigenSpi::Driver.clk_format must be one of :rl, :rh'
189
- end
190
-
191
- unless @ss_active == 0 || @ss_active == 1
192
- settings_valid = false
193
- Origen.log.error 'OrigenSpi::Driver.ss_active must be either 0 or 1'
194
- end
195
-
196
- @clk_multiple = 1 if @clk_multiple < 1
197
- @half_cycle = @clk_multiple / 2
198
-
199
- @miso_compare_cycle = @clk_multiple - 1 if @miso_compare_cycle > @clk_multiple - 1
200
-
201
- unless @data_order == :msb0 || @data_order == :lsb0
202
- settings_valid = false
203
- Origen.log.error 'OrigenSpi::Driver.data_order must be either :msb0 or :lsb0'
204
- end
205
-
206
- @settings_validated = settings_valid
207
- end
208
- end
209
-
210
- # Run a spi clock cycle
211
- #
212
- # This method can be used to clock the spi port without specifying shift data
213
- def sclk_cycle
214
- validate_settings
215
- cc 'OrigenSpi::Driver - Issue a clock cycle'
216
- @sclk_pin.restore_state do
217
- @sclk_pin.drive @clk_format == :rl ? 0 : 1
218
- @half_cycle.times { tester.cycle }
219
- @sclk_pin.drive @clk_format == :rl ? 1 : 0
220
- @half_cycle.upto(@clk_multiple - 1) { tester.cycle }
221
- end
222
- end
223
-
224
- # Shift a spi packet
225
- #
226
- # Overlay and capture is specified through reg.overlay and reg.store
227
- #
228
- # @example
229
- # spi_instance.shift master_out: out_reg, master_in: in_cmp_reg
230
- # @example
231
- # spi_instance.shift master_out: 0x7a, size: 32
232
- # @example
233
- # spi_instance.shift master_in: in_cmp_reg
234
- def shift(options = {})
235
- options = {
236
- master_out: 0,
237
- keep_ss_active: false
238
- }.merge(options)
239
-
240
- validate_settings
241
- options = validate_options(options)
242
- out_reg = build_output_packet(options).bits
243
- in_reg = build_input_packet(options).bits
244
-
245
- # reverse bit order if :msb0
246
- if @data_order == :msb0
247
- out_reg = out_reg.reverse
248
- in_reg = in_reg.reverse
249
- end
250
-
251
- # set ss active
252
- @ss_pin.drive @ss_active unless @ss_pin.nil?
253
-
254
- # apply wait time if requested
255
- tester.wait @clk_wait_time unless @clk_wait_time == { time_in_us: 0 }
256
-
257
- # now do the shifting
258
- 0.upto(out_reg.size - 1) do |bit|
259
- # park the clock
260
- @sclk_pin.drive @clk_format == :rl ? 0 : 1
261
- @miso_pin.dont_care unless @miso_pin.nil?
262
-
263
- # setup the bit to be driven, prep for overlay if requested
264
- overlay_options = {}
265
- unless @mosi_pin.nil?
266
- @mosi_pin.drive out_reg[bit].data
267
- if out_reg[bit].has_overlay?
268
- overlay_options[:pins] = @mosi_pin
269
- overlay_options[:overlay_str] = out_reg[bit].overlay_str
270
- end
271
- end
272
-
273
- # advance to clock active edge
274
- @half_cycle.times do |c|
275
- handle_miso c, in_reg[bit]
276
- cycle overlay_options
277
- overlay_options[:change_data] = false unless overlay_options == {}
278
- end
279
-
280
- # drive the clock to active
281
- @sclk_pin.drive @clk_format == :rl ? 1 : 0
282
-
283
- # advance to the end of the sclk cycle checking for appropriate miso compare placement
284
- @half_cycle.upto(@clk_multiple - 1) do |c|
285
- handle_miso c, in_reg[bit]
286
- cycle overlay_options
287
- end
288
- end
289
-
290
- # park the clock, mask miso, clear ss
291
- @sclk_pin.drive @clk_format == :rl ? 0 : 1
292
- @miso_pin.dont_care unless @miso_pin.nil?
293
- @mosi_pin.drive 0 unless @mosi_pin.nil?
294
- @ss_pin.drive @ss_active == 1 ? 0 : 1 unless @ss_pin.nil? || options[:keep_ss_active]
295
-
296
- # clear flags if registers were provided
297
- options[:master_out].clear_flags if options[:master_out].respond_to?(:clear_flags)
298
- options[:master_in].clear_flags if options[:master_in].respond_to?(:clear_flags)
299
- end
300
-
301
- # Internal method
302
- #
303
- # Set the state of miso
304
- def handle_miso(c, bit)
305
- unless @miso_pin.nil?
306
- if c == @miso_compare_cycle
307
- @miso_pin.assert bit.data if bit.is_to_be_read?
308
- tester.store_next_cycle @miso_pin if bit.is_to_be_stored?
309
- else
310
- @miso_pin.dont_care
311
- end
312
- end
313
- end
314
-
315
- # Internal method
316
- #
317
- # Issue a tester cycle, conditionally with overlay options supplied
318
- def cycle(overlay_options = {})
319
- overlay_options == {} ? tester.cycle : (tester.cycle overlay: overlay_options)
320
- end
321
-
322
- # Build the shift out packet
323
- #
324
- # This is an internal method used by the shift method
325
- def build_output_packet(options)
326
- out_reg = Origen::Registers::Reg.dummy(options[:size])
327
- if options[:master_out].respond_to?(:data)
328
- out_reg.copy_all(options[:master_out])
329
- else
330
- out_reg.write options[:master_out]
331
- end
332
- out_reg
333
- end
334
-
335
- # Build the input packet
336
- #
337
- # This is an internal method used by the shift method
338
- def build_input_packet(options)
339
- in_reg = Origen::Registers::Reg.dummy(options[:size])
340
- if options[:master_in].respond_to?(:data)
341
- in_reg.copy_all(options[:master_in])
342
- else
343
- unless options[:master_in].nil?
344
- in_reg.write options[:master_in]
345
- in_reg.read
346
- end
347
- end
348
- in_reg
349
- end
350
-
351
- # Internal method that logs errors in options passed to the shift method
352
- def validate_options(options)
353
- # check that size of the packet can be determined
354
- unless options[:size]
355
- options[:size] = options[:master_in].size if options[:master_in].respond_to?(:data)
356
- end
357
- unless options[:size]
358
- options[:size] = options[:master_out].size if options[:master_out].respond_to?(:data)
359
- end
360
- unless options[:size]
361
- Origen.log.error "OrigenSpi::Driver can't determine the packet size"
362
- exit
363
- end
364
- options
365
- end
366
- end
367
- end
1
+ module OrigenSpi
2
+ class Driver
3
+ include Origen::Model
4
+
5
+ # Dut pin that serves as the spi clock
6
+ # @example
7
+ # spi_instance.sclk_pin = dut.pin(:p1)
8
+ attr_reader :sclk_pin
9
+
10
+ # Dut pin that serves as the master out slave in pin
11
+ # @example
12
+ # spi_instance.mosi_pin = dut.pin(:p2)
13
+ attr_reader :mosi_pin
14
+
15
+ # Dut pin that serves as the master in slave out
16
+ # @example
17
+ # spi_instance.miso_pin = dut.pin(:p3)
18
+ attr_reader :miso_pin
19
+
20
+ # Dut pin that serves as the slave select
21
+ # @example
22
+ # spi_instance.ss_pin = dut.pin(:p4)
23
+ # @example
24
+ # spi_instance.ss_pin = nil # no ss pin
25
+ attr_reader :ss_pin
26
+
27
+ # clock format
28
+ #
29
+ # available options are:
30
+ # :rl # return low - data changes while sclk is low, latches on rising edge
31
+ # :rh # return high
32
+ #
33
+ # @example
34
+ # spi_instance.clk_format = :rl
35
+ attr_reader :clk_format
36
+
37
+ # pin state corresponding to slave select active
38
+ # @example
39
+ # spi_instance.ss_active = 0
40
+ attr_reader :ss_active
41
+
42
+ # time between ss_active and the first spi clock
43
+ #
44
+ # hash containing the wait time
45
+ #
46
+ # @example
47
+ # spi_instance.clk_wait_time = {time_in_us: 0} # no delay
48
+ # spi_instance.clk_wait_time = {time_in_ms: 1} # 1ms delay
49
+ # spi_instance.clk_wait_time = {time_in_cycles: 2} # 2 cycle delay
50
+ attr_reader :clk_wait_time
51
+
52
+ # number of tester cycles per spi clock
53
+ attr_reader :clk_multiple
54
+
55
+ # cycle on which miso compares are placed (0 is the first cycle)
56
+ attr_reader :miso_compare_cycle
57
+
58
+ # data order
59
+ #
60
+ # available options are:
61
+ # :msb0 - MSB is shifted first
62
+ # :lsb0
63
+ #
64
+ # @example
65
+ # spi_instance.data_order = :lsb0
66
+ attr_reader :data_order
67
+
68
+ # internal attribute
69
+ attr_reader :settings_validated
70
+
71
+ # options hash can configure
72
+ #
73
+ # @example
74
+ # options = {
75
+ # sclk_pin: dut.pin(:p1),
76
+ # mosi_pin: dut.pin(:p2),
77
+ # miso_pin: dut.pin(:p3),
78
+ # ss_pin: dut.pin(:p4),
79
+ # clk_format: :rl,
80
+ # ss_active: 0,
81
+ # clk_wait_time: {time_in_cycles: 2},
82
+ # clk_multiple: 2,
83
+ # miso_compare_cycle: 1,
84
+ # data_order: :msb0
85
+ # }
86
+ # spi = OrigenSpi::Driver.new(options)
87
+ def initialize(options = {})
88
+ options = {
89
+ sclk_pin: nil,
90
+ mosi_pin: nil,
91
+ miso_pin: nil,
92
+ ss_pin: nil,
93
+ clk_format: :nr,
94
+ ss_active: 0,
95
+ clk_wait_time: { time_in_us: 0 },
96
+ clk_multiple: 2,
97
+ data_order: :msb0
98
+ }.merge(options)
99
+
100
+ @sclk_pin = options[:sclk_pin]
101
+ @mosi_pin = options[:mosi_pin]
102
+ @miso_pin = options[:miso_pin]
103
+ @ss_pin = options[:ss_pin]
104
+ @clk_format = options[:clk_format]
105
+ @ss_active = options[:ss_active]
106
+ @clk_wait_time = options[:clk_wait_time]
107
+ @clk_multiple = options[:clk_multiple]
108
+ @miso_compare_cycle = options[:miso_compare_cycle] ? options[:miso_compare_cycle] : @clk_multiple - 1
109
+ @data_order = options[:data_order]
110
+ @settings_validated = false
111
+ end
112
+
113
+ def sclk_pin=(pin)
114
+ @sclk_pin = pin
115
+ @settings_validated = false
116
+ end
117
+
118
+ def mosi_pin=(pin)
119
+ @mosi_pin = pin
120
+ @settings_validated = false
121
+ end
122
+
123
+ def miso_pin=(pin)
124
+ @miso_pin = pin
125
+ @settings_validated = false
126
+ end
127
+
128
+ def ss_pin=(pin)
129
+ @ss_pin = pin
130
+ @settings_validated = false
131
+ end
132
+
133
+ def clk_format=(format)
134
+ if format == :nr || format == :rl || format == :rh
135
+ @clk_format = format
136
+ else
137
+ Origen.log.error "Invalid clock format for OrigenSpi::Driver -> #{format}"
138
+ Origen.log.error 'Valid formats are :rl, :rh'
139
+ end
140
+ @settings_validated = false
141
+ end
142
+
143
+ def ss_active=(state)
144
+ if state == 0 || state == 1
145
+ @ss_active = state
146
+ else
147
+ Origen.log.error 'Invalid OrigenSpi::Driver.ss_active state (valid states are 0 and 1)'
148
+ end
149
+ @settings_validated = false
150
+ end
151
+
152
+ def clk_wait_time=(clk_wait)
153
+ @clk_wait_time = clk_wait
154
+ @settings_validated = false
155
+ end
156
+
157
+ def clk_multiple=(mult)
158
+ @clk_multiple = mult
159
+ @settings_validated = false
160
+ end
161
+
162
+ def miso_compare_cycle=(cycle)
163
+ @miso_compare_cycle = cycle
164
+ @settings_validated = false
165
+ end
166
+
167
+ def data_order=(order)
168
+ if order == :msb0 || order == :lsb0
169
+ @data_order = order
170
+ else
171
+ Origen.log.error "Invalid OrigenSpi::Driver.data_order -> #{order}, (use :msb0 or :lsb0)"
172
+ end
173
+ end
174
+
175
+ # Check settings
176
+ def validate_settings
177
+ unless @settings_validated
178
+ settings_valid = true
179
+
180
+ # check that clock and miso are provided
181
+ unless @sclk_pin.is_a?(Origen::Pins::Pin)
182
+ settings_valid = false
183
+ Origen.log.error 'OrigenSpi::Driver.sclk_pin must be an Origen pin object'
184
+ end
185
+
186
+ unless @clk_format == :rl || @clk_format == :rh
187
+ settings_valid = false
188
+ Origen.log.error 'OrigenSpi::Driver.clk_format must be one of :rl, :rh'
189
+ end
190
+
191
+ unless @ss_active == 0 || @ss_active == 1
192
+ settings_valid = false
193
+ Origen.log.error 'OrigenSpi::Driver.ss_active must be either 0 or 1'
194
+ end
195
+
196
+ @clk_multiple = 1 if @clk_multiple < 1
197
+ @half_cycle = @clk_multiple / 2
198
+
199
+ @miso_compare_cycle = @clk_multiple - 1 if @miso_compare_cycle > @clk_multiple - 1
200
+
201
+ unless @data_order == :msb0 || @data_order == :lsb0
202
+ settings_valid = false
203
+ Origen.log.error 'OrigenSpi::Driver.data_order must be either :msb0 or :lsb0'
204
+ end
205
+
206
+ @settings_validated = settings_valid
207
+ end
208
+ end
209
+
210
+ # Run a spi clock cycle
211
+ #
212
+ # This method can be used to clock the spi port without specifying shift data
213
+ def sclk_cycle
214
+ validate_settings
215
+ cc 'OrigenSpi::Driver - Issue a clock cycle'
216
+ @sclk_pin.restore_state do
217
+ @sclk_pin.drive @clk_format == :rl ? 0 : 1
218
+ @half_cycle.times { tester.cycle }
219
+ @sclk_pin.drive @clk_format == :rl ? 1 : 0
220
+ @half_cycle.upto(@clk_multiple - 1) { tester.cycle }
221
+ end
222
+ end
223
+
224
+ # Shift a spi packet
225
+ #
226
+ # Overlay and capture is specified through reg.overlay and reg.store
227
+ #
228
+ # @example
229
+ # spi_instance.shift master_out: out_reg, master_in: in_cmp_reg
230
+ # @example
231
+ # spi_instance.shift master_out: 0x7a, size: 32
232
+ # @example
233
+ # spi_instance.shift master_in: in_cmp_reg
234
+ def shift(options = {})
235
+ options = {
236
+ master_out: 0,
237
+ keep_ss_active: false
238
+ }.merge(options)
239
+
240
+ validate_settings
241
+ options = validate_options(options)
242
+ out_reg = build_output_packet(options).bits
243
+ in_reg = build_input_packet(options).bits
244
+
245
+ # reverse bit order if :msb0
246
+ if @data_order == :msb0
247
+ out_reg = out_reg.reverse
248
+ in_reg = in_reg.reverse
249
+ end
250
+
251
+ # set ss active
252
+ @ss_pin.drive @ss_active unless @ss_pin.nil?
253
+
254
+ # apply wait time if requested
255
+ tester.wait @clk_wait_time unless @clk_wait_time == { time_in_us: 0 }
256
+
257
+ # now do the shifting
258
+ 0.upto(out_reg.size - 1) do |bit|
259
+ # note which bit this is
260
+ if @data_order == :msb0
261
+ thisbit = out_reg.size - 1 - bit
262
+ else
263
+ thisbit = bit
264
+ end
265
+
266
+ # park the clock
267
+ @sclk_pin.drive @clk_format == :rl ? 0 : 1
268
+ @miso_pin.dont_care unless @miso_pin.nil?
269
+
270
+ # setup the bit to be driven, prep for overlay if requested
271
+ overlay_options = {}
272
+ unless @mosi_pin.nil?
273
+ @mosi_pin.drive out_reg[bit].data
274
+ if out_reg[bit].has_overlay?
275
+ overlay_options[:pins] = @mosi_pin
276
+ overlay_options[:overlay_str] = out_reg[bit].overlay_str
277
+ end
278
+ end
279
+
280
+ # advance to clock active edge
281
+ @half_cycle.times do |c|
282
+ handle_miso c, in_reg[bit], thisbit
283
+ cycle overlay_options
284
+ overlay_options[:change_data] = false unless overlay_options == {}
285
+ end
286
+
287
+ # drive the clock to active
288
+ @sclk_pin.drive @clk_format == :rl ? 1 : 0
289
+
290
+ # advance to the end of the sclk cycle checking for appropriate miso compare placement
291
+ @half_cycle.upto(@clk_multiple - 1) do |c|
292
+ handle_miso c, in_reg[bit], thisbit
293
+ cycle overlay_options
294
+ end
295
+ end
296
+
297
+ # park the clock, mask miso, clear ss
298
+ @sclk_pin.drive @clk_format == :rl ? 0 : 1
299
+ @miso_pin.dont_care unless @miso_pin.nil?
300
+ @mosi_pin.drive 0 unless @mosi_pin.nil?
301
+ @ss_pin.drive @ss_active == 1 ? 0 : 1 unless @ss_pin.nil? || options[:keep_ss_active]
302
+
303
+ # clear flags if registers were provided
304
+ options[:master_out].clear_flags if options[:master_out].respond_to?(:clear_flags)
305
+ options[:master_in].clear_flags if options[:master_in].respond_to?(:clear_flags)
306
+ end
307
+
308
+ # Internal method
309
+ #
310
+ # Set the state of miso
311
+ def handle_miso(c, bit, bit_number)
312
+ unless @miso_pin.nil?
313
+ if c == @miso_compare_cycle
314
+ @miso_pin.assert bit.data, meta: { position: bit_number } if bit.is_to_be_read?
315
+ tester.store_next_cycle @miso_pin if bit.is_to_be_stored?
316
+ else
317
+ @miso_pin.dont_care
318
+ end
319
+ end
320
+ end
321
+
322
+ # Internal method
323
+ #
324
+ # Issue a tester cycle, conditionally with overlay options supplied
325
+ def cycle(overlay_options = {})
326
+ overlay_options == {} ? tester.cycle : (tester.cycle overlay: overlay_options)
327
+ end
328
+
329
+ # Build the shift out packet
330
+ #
331
+ # This is an internal method used by the shift method
332
+ def build_output_packet(options)
333
+ out_reg = Origen::Registers::Reg.dummy(options[:size])
334
+ if options[:master_out].respond_to?(:data)
335
+ out_reg.copy_all(options[:master_out])
336
+ else
337
+ out_reg.write options[:master_out]
338
+ end
339
+ out_reg
340
+ end
341
+
342
+ # Build the input packet
343
+ #
344
+ # This is an internal method used by the shift method
345
+ def build_input_packet(options)
346
+ in_reg = Origen::Registers::Reg.dummy(options[:size])
347
+ if options[:master_in].respond_to?(:data)
348
+ in_reg.copy_all(options[:master_in])
349
+ else
350
+ unless options[:master_in].nil?
351
+ in_reg.write options[:master_in]
352
+ in_reg.read
353
+ end
354
+ end
355
+ in_reg
356
+ end
357
+
358
+ # Internal method that logs errors in options passed to the shift method
359
+ def validate_options(options)
360
+ # check that size of the packet can be determined
361
+ unless options[:size]
362
+ options[:size] = options[:master_in].size if options[:master_in].respond_to?(:data)
363
+ end
364
+ unless options[:size]
365
+ options[:size] = options[:master_out].size if options[:master_out].respond_to?(:data)
366
+ end
367
+ unless options[:size]
368
+ Origen.log.error "OrigenSpi::Driver can't determine the packet size"
369
+ exit
370
+ end
371
+ options
372
+ end
373
+ end
374
+ end