ruby-adept 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.gitignore +17 -0
  2. data/.travis.yml +6 -0
  3. data/Gemfile +10 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +29 -0
  6. data/Rakefile +16 -0
  7. data/adept.gemspec +33 -0
  8. data/autotest/discover.rb +2 -0
  9. data/bin/bprog +110 -0
  10. data/firmware/.gitignore +73 -0
  11. data/firmware/epp_stream/Basys2_100_250General.ucf +21 -0
  12. data/firmware/epp_stream/epp_controller.vhd +210 -0
  13. data/firmware/epp_stream/epp_stream.xise +355 -0
  14. data/firmware/epp_stream/fifo.vhd +178 -0
  15. data/firmware/epp_stream/tests/fifo_testbench.vhdl +164 -0
  16. data/lib/adept/boards/basys2.rb +84 -0
  17. data/lib/adept/boards.rb +2 -0
  18. data/lib/adept/connection_provider.rb +30 -0
  19. data/lib/adept/data_formats/bitstream.rb +116 -0
  20. data/lib/adept/data_formats/data_factories.rb +33 -0
  21. data/lib/adept/data_formats.rb +2 -0
  22. data/lib/adept/device.rb +127 -0
  23. data/lib/adept/error.rb +4 -0
  24. data/lib/adept/jtag/connection.rb +404 -0
  25. data/lib/adept/jtag/device.rb +178 -0
  26. data/lib/adept/jtag/devices/fpga.rb +162 -0
  27. data/lib/adept/jtag/devices/null.rb +0 -0
  28. data/lib/adept/jtag/devices/platform_flash.rb +23 -0
  29. data/lib/adept/jtag/devices.rb +2 -0
  30. data/lib/adept/jtag/error.rb +8 -0
  31. data/lib/adept/jtag/tap_state.rb +67 -0
  32. data/lib/adept/jtag/tap_states.rb +52 -0
  33. data/lib/adept/jtag.rb +11 -0
  34. data/lib/adept/low_level/connection.rb +59 -0
  35. data/lib/adept/low_level/device.rb +43 -0
  36. data/lib/adept/low_level/device_error.rb +22 -0
  37. data/lib/adept/low_level/device_manager.rb +142 -0
  38. data/lib/adept/low_level/enhanced_parallel.rb +151 -0
  39. data/lib/adept/low_level/error_handler.rb +60 -0
  40. data/lib/adept/low_level/jtag.rb +379 -0
  41. data/lib/adept/low_level/library.rb +173 -0
  42. data/lib/adept/low_level.rb +4 -0
  43. data/lib/adept/version.rb +3 -0
  44. data/lib/adept.rb +11 -0
  45. data/spec/firmware/epp_loopback.bit +0 -0
  46. data/spec/lib/adept/data_formats/bitstream_spec.rb +95 -0
  47. data/spec/lib/adept/data_formats/data_factories_spec.rb +42 -0
  48. data/spec/lib/adept/device_spec.rb +88 -0
  49. data/spec/lib/adept/jtag/connection_spec.rb +433 -0
  50. data/spec/lib/adept/jtag/device_spec.rb +107 -0
  51. data/spec/lib/adept/jtag/devices/fpga_spec.rb +71 -0
  52. data/spec/lib/adept/low_level/enhanced_parallel_spec.rb +72 -0
  53. data/spec/lib/adept/low_level/jtag_spec.rb +204 -0
  54. data/spec/spec_helpers.rb +25 -0
  55. metadata +240 -0
@@ -0,0 +1,404 @@
1
+
2
+ require 'adept'
3
+ require 'adept/low_level'
4
+ require 'adept/jtag'
5
+
6
+ module Adept
7
+ module JTAG
8
+
9
+ #
10
+ # Represents a connection to a JTAG device.
11
+ #
12
+ class Connection
13
+ extend ConnectionProvider
14
+
15
+ attr_reader :tap_state
16
+
17
+ #
18
+ # Sets up a new JTAG connection.
19
+ #
20
+ def initialize(device, port_number=0)
21
+
22
+ #Store the information regarding the owning device...
23
+ @device = device
24
+
25
+ #Initialize the chain to zero until enumeration occurs.
26
+ @chain_length = 0
27
+ @devices_in_chain = 0
28
+
29
+ #... open a JTAG connection.
30
+ LowLevel::JTAG::EnableEx(@device.handle, port_number)
31
+
32
+ end
33
+
34
+ #
35
+ # Closes the given JTAG connection.
36
+ #
37
+ def close
38
+ LowLevel::JTAG::Disable(@device.handle)
39
+ end
40
+
41
+ #
42
+ # Returns a list of the JTAG IDCodes for all connected devices.
43
+ #
44
+ def connected_devices
45
+
46
+ #Reset all targets' TAPs; this will automatically load the IDCODE instruction into the
47
+ #instruction register
48
+ reset_target
49
+
50
+ devices = []
51
+ chain_length = 0
52
+ devices_in_chain = 0
53
+
54
+ #Loop until we've enumerated all devices in the JTAG chain.
55
+ loop do
56
+
57
+ #Recieve a single 32-bit JTAG ID code, LSB first.
58
+ idcode = receive_data(32, true)
59
+
60
+ #If we've recieved the special "null" IDcode, we've finished enumerating.
61
+ #(In this case, we'll choose to accept the technically-valid all-ones IDcode as null,
62
+ #as it is returned by most system boards when their power is turned off, and isn't
63
+ #otherwise supported.)
64
+ break if idcode == "\x00\x00\x00\x00" or idcode == "\xFF\xFF\xFF\xFF"
65
+
66
+ #Otherwise, add this idcode to the list...
67
+ devices << JTAG::Device.from_idcode(idcode.reverse, self, devices_in_chain, chain_length)
68
+
69
+ #... add its width the the known scan-chain length
70
+ chain_length += devices.last.instruction_width
71
+ devices_in_chain += 1
72
+
73
+ end
74
+
75
+ #Update the internal chain-length.
76
+ @chain_length = chain_length
77
+ @devices_in_chain = devices_in_chain
78
+
79
+ #Return the list of IDCodes.
80
+ devices.reverse
81
+
82
+ end
83
+
84
+ #
85
+ # Sets the state of the target's Test Access Port.
86
+ #
87
+ def tap_state=(new_state)
88
+
89
+ #If we're trying to enter the reset state, force a reset of the test hardware.
90
+ #(This ensure that we can reset the test hardware even if a communications (or target) error
91
+ # causes improper behavior.)
92
+ reset_target if new_state == JTAG::TAPStates::Reset
93
+
94
+ #If we're already in the desired state, abort.
95
+ return if new_state == @tap_state
96
+
97
+ #Find the correct sequence of TMS values to reach the desired state...
98
+ path = path_to_state(new_state).reverse
99
+ tms_values = [path.to_i(2)]
100
+
101
+ #... and apply them.
102
+ LowLevel::JTAG::transmit(@device.handle, tms_values, false, path.length, false)
103
+
104
+ #Update the internal record of the TAP state.
105
+ @tap_state = new_state
106
+
107
+ end
108
+
109
+ #
110
+ # Transmit an instruction over the JTAG test access lines, to be placed into
111
+ # the JTAG instruction register.
112
+ #
113
+ # bytes: A byte-string which contains the instruction to be transmitted.
114
+ # bit_count: The total amount of bits to be transmitted from the byte string.
115
+ #
116
+ # pad_to_chain_length:
117
+ # If set, the transmitted data will be suffixed with logic '1's until the chain length has been met.
118
+ # This allows the transmitter to easily put devices to the "left" of afttarget device into bypass.
119
+ #
120
+ # prefix_with_ones:
121
+ # Prefixes the transmitted data with the specified amount of logic '1's. Prefixing is skipped if this parameter
122
+ # is not provided, or is set to zero. This allows the transmitter to easily put devices to the "right" of a
123
+ # target device into bypass.
124
+ #
125
+ # do_not_finish:
126
+ # If set, the device will be left in the ShiftIR state, so additional instructions data be transmitted.
127
+ #
128
+ def transmit_instruction(bytes, bit_count, pad_to_chain_length=false, prefix_with_ones=0, do_not_finish=false)
129
+
130
+ #If the pad-to-chain length option is selected, compute the total amount of padding required.
131
+ #Otherwise, set the required padding to zero.
132
+ padding_after = pad_to_chain_length ? [@chain_length - prefix_with_ones - bit_count, 0].max : 0
133
+
134
+ #Move to the Exit1IR state after transmission, allowing the recieved data to be processed,
135
+ #unless the do_not_finish value is set.
136
+ state_after = do_not_finish ? nil : TAPStates::Exit1IR
137
+
138
+ #Transmit the actual instruction.
139
+ transmit_in_state(TAPStates::ShiftIR, bytes, bit_count, state_after, true, prefix_with_ones, padding_after)
140
+
141
+ end
142
+
143
+ #
144
+ # Transmit data over the JTAG test access lines, to be placed into
145
+ # the JTAG data register.
146
+ #
147
+ # bytes: A byte-string which contains the instruction to be transmitted.
148
+ # bit_count: The total amount of bits to be transmitted from the byte string.
149
+ #
150
+ # pad_to_chain_length:
151
+ # If set, the transmitted data will be suffixed with logic '1's until the chain length has been met,
152
+ # *assuming that all devices other than the single target device are in bypass*.
153
+ # This allows the transmitter to easily fill the bypass registers of all additional devices with zeroes.
154
+ #
155
+ # prefix_with_zeroes:
156
+ # Prefixes the transmitted data with the specified amount of logic '0's. Prefixing is skipped if this parameter
157
+ # is not provided, or is set to zero.
158
+ #
159
+ # do_not_finish:
160
+ # If set, the device will be left in the ShiftIR state, so additional instructions data be transmitted.
161
+ #
162
+ def transmit_data(bytes, bit_count, pad_to_chain_length=false, prefix_with_zeroes=0, do_not_finish=false)
163
+
164
+ #If the pad-to-chain length option is selected, compute the total amount of padding required.
165
+ #Otherwise, set the required padding to zero.
166
+ padding_after = pad_to_chain_length ? [@devices_in_chain - prefix_with_zeroes - 1, 0].max : 0
167
+
168
+ #Move to the Exit1IR state after transmission, allowing the recieved data to be processed,
169
+ #unless the do_not_finish value is set.
170
+ state_after = do_not_finish ? nil : TAPStates::Exit1DR
171
+
172
+ #Transmit the actual instruction.
173
+ transmit_in_state(TAPStates::ShiftDR, bytes, bit_count, state_after, false, prefix_with_zeroes, padding_after)
174
+
175
+ end
176
+
177
+
178
+ #
179
+ # Recieve data from the JTAG data register.
180
+ #
181
+ # bit_count: The amount of bits to receive.
182
+ # do_not_finish: If set, the transmission will be "left open" so additional data can be received.
183
+ #
184
+ def receive_data(bit_count, do_not_finish=false, overlap=false)
185
+
186
+ #Put the device into the desired state.
187
+ self.tap_state = JTAG::TAPStates::ShiftDR
188
+
189
+ #Transmit the data, and recieve the accompanying response.
190
+ response = LowLevel::JTAG::receive(@device.handle, false, false, bit_count, overlap)
191
+
192
+ #If a state_after was provided, place the device into that state.
193
+ self.tap_state = JTAG::TAPStates::Exit1DR unless do_not_finish
194
+
195
+ #Return the received response.
196
+ response
197
+
198
+ end
199
+
200
+ #
201
+ # Switches to run/test mode, and holds that state for the desired amount of clock ticks.
202
+ #
203
+ def run_test(clock_ticks)
204
+
205
+ #Put the target into the Run-Test-Idle state.
206
+ self.tap_state = JTAG::TAPStates::Idle
207
+
208
+ #And "tick" the test clock for the desired amount of cycles.
209
+ LowLevel::JTAG::tick(@device.handle, false, false, clock_ticks, false)
210
+
211
+ end
212
+
213
+ #
214
+ # Force-resets the target device.
215
+ #
216
+ def reset_target
217
+
218
+ #Reset the target device's JTAG controller by sending five bits of TMS='1'.
219
+ LowLevel::JTAG::tick(@device.handle, true, false, 5)
220
+
221
+ #Set the internal TAP state to reset.
222
+ @tap_state = JTAG::TAPStates::Reset
223
+
224
+ end
225
+
226
+ #
227
+ # Registers a device type to be handled by JTAG connections, allowing JTAGDevice
228
+ # instances to be automatically created upon device enumeration.
229
+ #
230
+ # Device types typically are classes which include the JTAGDevice mixin,
231
+ #
232
+ #
233
+ def self.register_device_type(type)
234
+ @device_types << type
235
+ end
236
+
237
+ #
238
+ # Determines if the given device can serve as the host for a JTAG connection.
239
+ #
240
+ def self.supported_by?(device)
241
+ LowLevel::JTAG::supported?(device)
242
+ end
243
+
244
+ private
245
+
246
+ #
247
+ # Transmits a sequence of data while in a given state.
248
+ #
249
+ # bytes: A byte-string which contains the instruction to be transmitted;
250
+ # or a boolean value to send a single bit repeatedly.
251
+ # bit_count: The total amount of bits to be transmitted.
252
+ #
253
+ #
254
+ def transmit_in_state(state_before, value, bit_count, state_after=nil, pad_with=false, pad_before=0, pad_after=0)
255
+
256
+ #Put the device into the desired state.
257
+ self.tap_state = state_before
258
+
259
+ #If we've been instructed to pad before the transmission, do so.
260
+ LowLevel::JTAG::transmit(@device.handle, false, pad_with, pad_before) unless pad_before.zero?
261
+
262
+ #Transmit the data, and receive the accompanying response.
263
+ response = transmit_and_advance(false, value, bit_count, pad_after.zero?() ? state_after : nil)
264
+
265
+ #If we've been instructed to pad after the transmission, do so.
266
+ transmit_and_advance(false, pad_with, pad_after, state_after) unless pad_after.zero?
267
+
268
+ #If a state_after was provided, place the device into that state.
269
+ self.tap_state = state_after unless state_after.nil?
270
+
271
+ #Return the received response.
272
+ response
273
+
274
+ end
275
+
276
+ #
277
+ # Performs a data transmission, and advances TMS.
278
+ #
279
+ # The final TMS value is overlapped with the last bit, so the final shift and
280
+ # state change occur on the same clock edge.
281
+ #
282
+ # tms: The TMS value to use for all but the last bit of the transmission.
283
+ # tdi: The TDI values to transmit, in the same format accepted by the other transmit functions.
284
+ # bit_count: The total amount of bits to transmit.
285
+ # advance_towards:
286
+ # The state to advance towards after the transmission is complete. Used to
287
+ # determine the last value of TMS. If advance_towards is nil, the TAP state
288
+ # will not be advanced- this is useful for conditionally advancing the state.
289
+ #
290
+ def transmit_and_advance(tms, tdi, bit_count, advance_towards)
291
+
292
+ #If we don't have a state to advance towards, perform a normal transmit, and return.
293
+ unless advance_towards
294
+ return LowLevel::JTAG::transmit(@device.handle, tms, tdi, bit_count, false)
295
+ end
296
+
297
+ #If we were passed a byte array, pack it into a string
298
+ tdi = tdi.pack("C*") if tdi.respond_to?(:pack)
299
+
300
+ #Transmit all but the last bit of the TDI data.
301
+ main_response = LowLevel::JTAG::transmit(@device.handle, tms, tdi, bit_count - 1, false)
302
+
303
+ #Figure out what TMS should be during the last transmission by checking the path
304
+ #to the state we want to advance towards.
305
+ last_tms, next_state = @tap_state.next_hop_towards(advance_towards)
306
+
307
+ #Transmit the last bit of the TDI data, with the final TMS value.
308
+ last_tdi = bit_of_message(tdi, bit_count - 1)
309
+ last_response = LowLevel::JTAG::transmit(@device.handle, last_tms == 1, last_tdi, 1, false)
310
+
311
+ #Update the internal TAP fsm.
312
+ @tap_state = next_state
313
+
314
+ #Compose a single byte-string response by merging the response from the
315
+ #first and second transmissions.
316
+ return add_bit_to_message(main_response, bit_count - 1, last_response)
317
+
318
+ end
319
+
320
+ #
321
+ # Returns the appropriate bit of a given message.
322
+ #
323
+ def bit_of_message(string, bit_number)
324
+
325
+ #If were passed a non-unpackable element, return it directly.
326
+ return string unless string.respond_to?(:unpack)
327
+
328
+ #Break the message down into an array of bytes
329
+ bytes = string.unpack("B*").first
330
+
331
+ #... and return the requested bit, as a boolean.
332
+ bytes[-bit_number - 1] == "1"
333
+
334
+ end
335
+
336
+ #
337
+ # Appends a given bit to a message.
338
+ #
339
+ # message: The message to which the bit is to be appended, as a byte-string.
340
+ # message_length: The message's length, in bits.
341
+ # bit: The bit to be appended, as a boolean.
342
+ #
343
+ def add_bit_to_message(message, message_length, bit)
344
+
345
+ #If we don't have a message, return the bit as a string.
346
+ return bit ? "\x01" : "\x00" if message_length.zero?
347
+
348
+ #If we have something other than an integer, consider its truthiness
349
+ #in the same way that C would.
350
+ bit = bit.unpack("C*").first.nonzero? if bit.respond_to?(:unpack)
351
+
352
+ #Convert the message into a sequence of binary bits.
353
+ message = message.unpack("B*").first
354
+
355
+ #Ensure that the message is zero-padded to be at least the message length.
356
+ message = message.rjust(message_length, '0')
357
+
358
+ #Extract all of the bits up to the bit count, and add the new bit.
359
+ message = (bit ? '1' : '0') + message[-message_length..-1]
360
+
361
+ #Convert the message back into a packed string, and return it.
362
+ [message].pack("B*")
363
+
364
+ end
365
+
366
+ #
367
+ # Find the shortest "path" (sequence of most-select values) which will
368
+ # put the JTAG TAP FSM into the desired state.
369
+ #
370
+ # Note that the next-hop-towards algorithms do not consider "impossible"
371
+ # combinations, such as a jump from EXIT1DR to EXIT2DR; these may cause
372
+ # an infinite loop.
373
+ #
374
+ def path_to_state(destination, start=nil)
375
+
376
+ #Create a "state pointer", which will be used to trace the FSM in order to
377
+ #find a path to the destination state. If no start was provided, use the
378
+ #current TAP state.
379
+ state = start || @tap_state
380
+
381
+ path = ""
382
+
383
+ #Traverse the FSM until we reach our destination.
384
+ until state == destination
385
+
386
+ #Find the next hop on the path to the destination...
387
+ next_hop, _ = state.next_hop_towards(destination)
388
+
389
+ #Move the "state pointer" to the next state, simulating a traversal
390
+ #of the Finite State Machine.
391
+ state = state.next_state(next_hop)
392
+
393
+ #And add the hop to the path
394
+ path << next_hop.to_s
395
+
396
+ end
397
+
398
+ #And return the computed path.
399
+ path
400
+
401
+ end
402
+ end
403
+ end
404
+ end
@@ -0,0 +1,178 @@
1
+
2
+ module Adept
3
+ module JTAG
4
+
5
+ #
6
+ # Generic JTAG Device.
7
+ #
8
+ # This class primarily exists to serve as a base for custom JTAG devices,
9
+ # but also can be used to represent an unknown JTAG device.
10
+ #
11
+ class Device
12
+
13
+ attr_accessor :idcode
14
+
15
+ #Assume an instruction width of 4; the minimum possible insruction width.
16
+ #This should be re-defined in each inheriting class.
17
+ InstructionWidth = 4
18
+
19
+ # An internal list of device types which can be recognized
20
+ # on a JTAG bus.
21
+ @device_types = []
22
+
23
+ #
24
+ # Hook which is executed when a class inherits from the JTAG Device
25
+ # class. Registers the class as a Device Type provider, and sets up
26
+ # the device's basic metaprogramming abilities.
27
+ #
28
+ def self.inherited(klass)
29
+
30
+ #Register the class as device-type provider...
31
+ @device_types << klass
32
+
33
+ #And set up the "supports_idcode" metaprogramming facility.
34
+ klass.instance_variable_set(:@supported_idcodes, [])
35
+
36
+ end
37
+
38
+ #
39
+ # Factory method which creates a new Device whose type is determined
40
+ # by the provided IDCode.
41
+ #
42
+ # idcode: The IDCode of the new JTAG device.
43
+ # position_in_chain:
44
+ # The device's position in the chain. The first device to recieve data will have
45
+ # the highest device number. In a two device chain, Device 1 will recieve data before
46
+ # Device 0.
47
+ # chain_offset:
48
+ # The amount of bits which must be transmitted to other devices before an instruction
49
+ # can be transmitted to this device- equal to the amount of bits to the _right_ of the
50
+ # active device on the scan chain.
51
+ #
52
+ def self.from_idcode(idcode, connection, position_in_chain, chain_offset)
53
+
54
+ #Find the first device type which supports the IDCode.
55
+ device_type = @device_types.find { |type| type.supports?(idcode) }
56
+
57
+ #If we weren't able to find a device, use this class as a generic wrapper.
58
+ device_type ||= self
59
+
60
+ #Otherwise, instantiate tha new device from the device type.
61
+ device_type.new(idcode, connection, position_in_chain, chain_offset)
62
+
63
+ end
64
+
65
+ #
66
+ # Default implementation for detection of IDCode support.
67
+ # Checks to see if any of the IDcode matches any of this class's masks.
68
+ #
69
+ def self.supports?(idcode)
70
+ @supported_idcodes.any? { |mask| idcode_matches_mask(mask, idcode) }
71
+ end
72
+
73
+
74
+ #
75
+ # Returns the expected instruction width of the JTAG device.
76
+ #
77
+ # In this case, we don't know what the instruction width will be,
78
+ # so we'll assume the minimum possible width of four bits.
79
+ #
80
+ def instruction_width
81
+ return self.class::InstructionWidth
82
+ end
83
+
84
+ #
85
+ # idcode: The IDCode of the new JTAG device.
86
+ # scan_offset:
87
+ # The amount of bits which must be transmitted to other devices before an instruction
88
+ # can be transmitted to this device- equal to the amount of bits to the _right_ of the
89
+ # active device on the scan chain.
90
+ #
91
+ #
92
+ def initialize(idcode, connection, position_in_chain, chain_offset)
93
+ @idcode = idcode
94
+ @connection = connection
95
+ @position_in_chain = position_in_chain
96
+ @chain_offset = chain_offset
97
+ end
98
+
99
+ #
100
+ # Activate the device, and set its current operating instruction.
101
+ # All other devices in the scan chain are placed into BYPASS.
102
+ #
103
+ def instruction=(instruction)
104
+
105
+ #If we were provided an instruction name, look up the corresponding instruction.
106
+ instruction = self.class::Instructions[instruction] if instruction.kind_of?(Symbol)
107
+
108
+ #If we have a packable number, pack it into a byte-string.
109
+ instruction = [instruction].pack("C*") if instruction.kind_of?(Numeric)
110
+
111
+ #Transmit the instruction itself.
112
+ @connection.transmit_instruction(instruction, instruction_width, true, @chain_offset)
113
+ end
114
+
115
+ #
116
+ # TODO: Handle instruction readback, by rotating instructions through the device.?
117
+ #
118
+
119
+ #
120
+ # Send data directly to (and receive data directly from) the given device.
121
+ # Assumes the current device is active, and all other devices are in bypass.
122
+ #
123
+ def transmit_data(data, bit_count=data.length * 8)
124
+ @connection.transmit_data(data, bit_count, true, @position_in_chain)
125
+ end
126
+
127
+ #
128
+ # Recieves data directly from the given device by sending the device an
129
+ # appropriately-sized string of zeroes.
130
+ # Assumes the current device is active, and all other devices are in bypass.
131
+ #
132
+ #
133
+ def receive_data(bit_count)
134
+ @connection.transmit_data(false, bit_count, true, @position_in_chain, true)
135
+ end
136
+
137
+ #
138
+ # Allows the device to run its test operation for a certain amount of TCK cycles.
139
+ # (Delegates the run_test operation to the JTAG connection object, which is in charge
140
+ # of the TAP state.)
141
+ #
142
+ def run_test(cycles)
143
+ @connection.run_test(cycles)
144
+ end
145
+
146
+ private
147
+
148
+ #
149
+ # Metaprogramming routine which indicates that the class being defined
150
+ # supports an IDcode mask.
151
+ #
152
+ def self.supports_idcode(*idcodes)
153
+
154
+ #And merge them with the known supported IDcodes.
155
+ @supported_idcodes |= idcodes
156
+
157
+ end
158
+
159
+ #
160
+ # Determines if a given IDCode matches a hex mask.
161
+ #
162
+ def self.idcode_matches_mask(mask, idcode)
163
+
164
+ #Convert the IDcode into a string, for comparison.
165
+ idcode = idcode.unpack("H*").first.downcase
166
+
167
+ #Get a set of pairs containing the characters with the same position in each string.
168
+ character_pairs = mask.downcase.chars.zip(idcode.chars)
169
+
170
+ #And verify that each character is either a match, or a Don't Care.
171
+ character_pairs.all? { |m, i| m == 'x' || m == i }
172
+
173
+ end
174
+
175
+ end
176
+
177
+ end
178
+ end