origen_spi 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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