katcp 0.0.2 → 0.0.3

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.
@@ -4,12 +4,132 @@ require 'katcp/client'
4
4
  module KATCP
5
5
 
6
6
  # Facilitates talking to <tt>tcpborphserver2</tt>, a KATCP server
7
- # implementation that runs on ROACH boards. Mostly just adds convenience
8
- # methods for tab completion in irb.
7
+ # implementation that runs on ROACH boards. In addition to providing
8
+ # convenience wrappers around <tt>tcpborphserver2</tt> requests, it also adds
9
+ # the following features:
9
10
  #
10
- # TODO: Add conveneince methods for converting binary payload data.
11
+ # * Hash-like access to read and write gateware devices (e.g. software
12
+ # registers, shared BRAM, etc.) via methods #[] and #[]=.
13
+ #
14
+ # * Each RoachClient instance dynamically adds (and removes) reader and
15
+ # writer attributes (i.e. methods) for gateware devices as the FPGA is
16
+ # programmed (or de-programmed). This not only provides for very clean and
17
+ # readable code, it also provides convenient tab completion in irb for
18
+ # gateware specific device names.
19
+ #
20
+ # * Word based instead of byte based data offsets and counts. KATCP::Client
21
+ # data transfer methods #read and #write deal with byte based offsets and
22
+ # counts. These methods in KATCP::RoachClient deal with word based offsets
23
+ # and counts since ROACH transfers (currently) require word alignment in
24
+ # both offsets and counts. To use byte based offsets and counts
25
+ # explicitly, use <tt>request(:read, ...)</tt> instead of
26
+ # <tt>read(...)</tt> etc.
11
27
  class RoachClient < Client
12
28
 
29
+ # Returns an Array of Strings representing the device names from the
30
+ # current design. The Array will be empty if the currently programmed
31
+ # gateware has no devices (very rare, if even possible) or if no gateware
32
+ # is currently programmed.
33
+ attr_reader :devices
34
+
35
+ # Creates a RoachClient that connects to a KATCP server at +remote_host+ on
36
+ # +remote_port+. If +local_host+ and +local_port+ are specified, then
37
+ # those parameters are used on the local end to establish the connection.
38
+ def initialize(remote_host, remote_port=7147, local_host=nil, local_port=nil)
39
+ super(remote_host, remote_port, local_host, local_port)
40
+ # List of all devices
41
+ @devices = [];
42
+ # List of dynamically defined device attrs (readers only, writers implied)
43
+ @device_attrs = [];
44
+ define_device_attrs
45
+ end
46
+
47
+ # Dynamically define attributes (i.e. methods) for gateware devices, if
48
+ # currently programmed.
49
+ def define_device_attrs # :nodoc
50
+ # First undefine existing device attrs
51
+ undefine_device_attrs
52
+ # Define nothing if FPGA not programmed
53
+ return unless programmed?
54
+ # Dynamically define accessors for all devices (i.e. registers, BRAMs,
55
+ # etc.) except those whose names conflict with existing methods.
56
+ @devices = listdev.lines[0..-2].map {|l| l[1]}
57
+ @devices.sort!
58
+ @devices.each do |dev|
59
+ # TODO sanitize dev in case it is invalid method name
60
+
61
+ # Define methods unless they conflict with existing methods
62
+ if ! respond_to?(dev) && ! respond_to?("#{dev}=")
63
+ # Save attr name
64
+ @device_attrs << dev
65
+ # Dynamically define methods
66
+ instance_eval <<-"_end"
67
+ def #{dev}(*args)
68
+ read('#{dev}', *args)
69
+ end
70
+ def #{dev}=(*args)
71
+ write('#{dev}', 0, *args)
72
+ end
73
+ _end
74
+ end
75
+ end
76
+ self
77
+ end
78
+
79
+ protected :define_device_attrs
80
+
81
+ # Undefine any attributes (i.e. methods) that were previously defined
82
+ # dynamically.
83
+ def undefine_device_attrs # :nodoc
84
+ @device_attrs.each do |dev|
85
+ instance_eval <<-"_end"
86
+ class << self
87
+ remove_method '#{dev}'
88
+ remove_method '#{dev}='
89
+ end
90
+ _end
91
+ end
92
+ @device_attrs.clear
93
+ @devices.clear
94
+ self
95
+ end
96
+
97
+ protected :undefine_device_attrs
98
+
99
+ # Returns +true+ if the current design has a device named +device+.
100
+ def has_device?(device)
101
+ @devices.include?(device.to_s)
102
+ end
103
+
104
+ # Provides Hash-like querying for a device named +device+.
105
+ alias has_key? has_device?
106
+
107
+ # call-seq:
108
+ # bulkread(register_name) -> Integer
109
+ # bulkread(register_name, word_offset) -> Integer
110
+ # bulkread(register_name, word_offset, word_count) -> NArray.int(word_count)
111
+ #
112
+ # Reads a +word_count+ words starting at +word_offset+ offset from
113
+ # register (or block RAM) named by +register_name+. Returns an Integer
114
+ # unless +word_count+ is given in which case it returns an
115
+ # NArray.int(word_count).
116
+ #
117
+ # Equivalent to #read, but uses a bulkread request rather than a read
118
+ # request.
119
+ def bulkread(register_name, *args)
120
+ byte_offset = 4 * (args[0] || 0)
121
+ byte_count = 4 * (args[1] || 1)
122
+ raise 'word count must be non-negative' if byte_count < 0
123
+ resp = request(:bulkread, register_name, byte_offset, byte_count)
124
+ raise resp.to_s unless resp.ok?
125
+ data = resp.lines[0..-2].map{|l| l[1]}.join
126
+ if args.length <= 1
127
+ data.unpack('N')[0]
128
+ else
129
+ data.to_na(NArray::INT).ntoh
130
+ end
131
+ end
132
+
13
133
  # call-seq:
14
134
  # delbof(image_file) -> KATCP::Response
15
135
  #
@@ -48,22 +168,56 @@ module KATCP
48
168
  #
49
169
  # Programs a gateware image specified by +image_file+. If +image_file+ is
50
170
  # omitted, de-programs the FPGA.
171
+ #
172
+ # Whenever the FPGA is programmed, reader and writer attributes (i.e.
173
+ # methods) are defined for every device listed by #listdev except for
174
+ # device names that conflict with an already existing method names.
175
+ #
176
+ # Whenever the FPGA is de-programmed (or re-programmed), existing
177
+ # attributes that were dynamically defined for the previous design are
178
+ # removed.
51
179
  def progdev(*args)
52
180
  request(:progdev, *args)
181
+ define_device_attrs
182
+ end
183
+
184
+ # Returns true if currently programmed (specifically, it is equivalent to
185
+ # <tt>status.ok?</tt>).
186
+ def programmed?
187
+ status.ok?
53
188
  end
54
189
 
55
190
  # call-seq:
56
- # read(register_name) -> KATCP::Response
57
- # read(register_name, register_offset) -> KATCP::Response
58
- # read(register_name, register_offset, byte_count) -> KATCP::Response
191
+ # read(register_name) -> Integer
192
+ # read(register_name, word_offset) -> Integer
193
+ # read(register_name, word_offset, word_count) -> NArray.int(word_count)
194
+ #
195
+ # Reads one or +word_count+ words starting at +word_offset+ offset from
196
+ # register (or block RAM) named by +register_name+. Returns an Integer
197
+ # unless +word_count+ is given in which case it returns an
198
+ # NArray.int(word_count).
59
199
  #
60
- # Reads a +byte_count+ bytes starting at +register_offset+ offset from
61
- # register (or block RAM) named by +register_name+. Binary data can be
62
- # obtained from the Response via the Response#payload method.
200
+ # Note that KATCP::Client#read deals with byte based offsets and counts,
201
+ # but all reads on the ROACH must be word aligned and an integer number of
202
+ # words long, so KATCP::RoachClient#read deals with word based offsets and
203
+ # counts.
63
204
  def read(register_name, *args)
64
- request(:read, register_name, *args)
205
+ byte_offset = 4 * (args[0] || 0)
206
+ byte_count = 4 * (args[1] || 1)
207
+ raise 'word count must be non-negative' if byte_count < 0
208
+ resp = request(:read, register_name, byte_offset, byte_count)
209
+ raise resp.to_s unless resp.ok?
210
+ data = resp.payload
211
+ if args.length <= 1
212
+ data.unpack('N')[0]
213
+ else
214
+ data.to_na(NArray::INT).ntoh
215
+ end
65
216
  end
66
217
 
218
+ alias wordread read
219
+ alias [] read
220
+
67
221
  # call-seq:
68
222
  # status -> KATCP::Response
69
223
  #
@@ -97,7 +251,7 @@ module KATCP
97
251
  #
98
252
  # Stop a tgtap instance.
99
253
  def tap_stop(register_name)
100
- reqest(:tap_stop, register_name)
254
+ request(:tap_stop, register_name)
101
255
  end
102
256
 
103
257
  # call-seq:
@@ -112,30 +266,39 @@ module KATCP
112
266
  end
113
267
 
114
268
  # call-seq:
115
- # wordread(register_name) -> KATCP::Response
116
- # wordread(register_name, word_offset) -> KATCP::Response
117
- # wordread(register_name, word_offset, length) -> KATCP::Response
118
- #
119
- # Reads word(s) from +register_name+.
120
- def wordread(register_name, *args)
121
- request(:wordread, register_name, *args)
122
- end
123
-
124
- # call-seq:
125
- # wordwrite(register_name, word_offset, payload[, ...]) -> KATCP::Response
269
+ # write(register_name, data) -> self
270
+ # write(register_name, word_offset, data) -> self
126
271
  #
127
- # Writes one or more words to a register (or block RAM).
128
- def wordwrite(register_name, word_offset, *args)
129
- request(:wordwrite, register_name, word_offset, *args)
272
+ # Write +data+ to +word_offset+ (0 if not given) in register named
273
+ # +register_name+. The +data+ argument can be a String containing raw
274
+ # bytes (byte length must be multiple of 4), NArray.int, Array of integer
275
+ # values, or other object that responds to #to_i.
276
+ def write(register_name, *args)
277
+ word_offset = (args.length > 1) ? args.shift : 0
278
+ byte_offset = 4 * word_offset
279
+ args.flatten!
280
+ args.map! do |a|
281
+ case a
282
+ when String; a
283
+ when NArray; a.hton.to_s
284
+ when Array; a.pack('N*')
285
+ else [a.to_i].pack('N*')
286
+ end
287
+ end
288
+ data = args.join
289
+ byte_count = data.length
290
+ if byte_count % 4 != 0
291
+ raise "data length of #{byte_count} bytes is not a multiple of 4 bytes"
292
+ elsif byte_count == 0
293
+ warn "writing 0 bytes to #{register_name}"
294
+ end
295
+ resp = request(:write, register_name, register_offset, data)
296
+ raise resp.to_s unless resp.ok?
297
+ self
130
298
  end
131
299
 
132
- # call-seq:
133
- # write(register_name, register_offset, data_payload) -> KATCP::Response
134
- #
135
- # Write a given payload to an offset in a register.
136
- def write(register_name, register_offset, data_payload)
137
- request(:write, register_name, register_offset, data_payload)
138
- end
300
+ alias wordwrite write
301
+ alias []= write
139
302
 
140
303
  end # class RoachClient
141
304
  end # module KATCP
data/lib/katcp/client.rb CHANGED
@@ -45,7 +45,7 @@ module KATCP
45
45
  # "\n".
46
46
  while line = @socket.gets("\n") do
47
47
  # Split line into words and unescape each word
48
- words = line.split.map! {|w| w.katcp_unescape!}
48
+ words = line.chomp.split(/[ \t]+/).map! {|w| w.katcp_unescape!}
49
49
  # Handle requests, replies, and informs based on first character of
50
50
  # first word.
51
51
  case words[0][0,1]
@@ -162,26 +162,13 @@ module KATCP
162
162
  end
163
163
 
164
164
  # call-seq:
165
- # payload -> String or nil
166
- # payload(:to_i) -> Integer or nil
167
- # payload(:unpack, 'N*') -> Array_of_Integers or nil
168
- # payload(:to_na, NArray::INT) -> NArray_of_ints or nil
169
- # etc...
165
+ # payload -> String
170
166
  #
171
167
  # Returns contents of reply line ("words" joined by spaces) after status
172
- # word if <tt>ok?</tt> returns true. Returns +nil+ if <tt>ok?</tt> is
173
- # false or no payload exists. If +args+ are given, they are sent to the
174
- # payload String (via String#send) and the results are returned.
175
- #
176
- # For example, passing <tt>:to_i</tt> will result in conversion of payload
177
- # String to Integer via String#to_i. The String class can be
178
- # monkey-patched as needed for additional conversion options.
179
- def payload(*args)
180
- if ok?
181
- s = @lines[-1][2..-1].join(' ')
182
- return s if args.empty?
183
- s.send(*args)
184
- end
168
+ # word if <tt>ok?</tt> returns true. Returns an empty string if
169
+ # <tt>ok?</tt> is false or no payload exists.
170
+ def payload
171
+ ok? ? @lines[-1][2..-1].join(' ') : ''
185
172
  end
186
173
 
187
174
  # call-seq:
data/lib/katcp/util.rb CHANGED
@@ -8,7 +8,7 @@ class String
8
8
  def katcp_escape!
9
9
  empty? ? self[0..-1] = '\@' : self.gsub!(/[\\ \0\n\r\e\t]/) do |s|
10
10
  case s
11
- when "\\": '\\'
11
+ when "\\": '\\\\'
12
12
  when " " : '\_'
13
13
  when "\0": '\0'
14
14
  when "\n": '\n'
@@ -29,7 +29,7 @@ class String
29
29
  def katcp_unescape!
30
30
  self == '\@' ? self[0..-1] = '' : self.gsub!(/\\[\\_0nret]/) do |s|
31
31
  case s
32
- when '\\': "\\"
32
+ when '\\\\': "\\"
33
33
  when '\_': " "
34
34
  when '\0': "\0"
35
35
  when '\n': "\n"
@@ -48,26 +48,10 @@ class String
48
48
 
49
49
  # call-seq:
50
50
  # to_na(typecode) -> NArray
51
- # to_na(typecode, byteswap) -> NArray
52
- # to_na(typecode,size[, ...]) -> NArray
53
- # to_na(typecode,size[, ...], byteswap) -> NArray
51
+ # to_na(typecode, size[, ...]) -> NArray
54
52
  #
55
- # Convert String to NArray accoring to +typecode+ and call byte swap method
56
- # given by Symbol +byteswap+. Pass +nil+ for +byteswap+ to skip the byte
57
- # swap conversion.
58
- #
59
- # Because, as of this writing, KATCP servers typically run on big endian
60
- # systems which return binary payload data in network byte order, the default
61
- # +byteswap+ is <tt>:ntoh</tt>, which converts from network byte order to
62
- # host (i.e. native) order.
53
+ # Convert String to NArray accoring to +typecode+.
63
54
  def to_na(typecode, *args)
64
- # Default to :ntoh
65
- byteswap = :ntoh
66
- if !args.empty? && (Symbol === args[-1] || args[-1].nil?)
67
- byteswap = args.pop
68
- end
69
- na = NArray.to_na(self, typecode, *args)
70
- na = na.send(byteswap) if byteswap
71
- na
55
+ NArray.to_na(self, typecode, *args)
72
56
  end
73
57
  end
data/lib/katcp/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  #++
4
4
 
5
5
  module KATCP
6
- VERSION = "0.0.2"
6
+ VERSION = "0.0.3"
7
7
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 2
9
- version: 0.0.2
8
+ - 3
9
+ version: 0.0.3
10
10
  platform: ruby
11
11
  authors:
12
12
  - David MacMahon
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-07-20 00:00:00 -07:00
17
+ date: 2010-10-21 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -59,7 +59,7 @@ rdoc_options:
59
59
  - -m
60
60
  - README
61
61
  - --title
62
- - Ruby/KATCP 0.0.2 Documentation
62
+ - Ruby/KATCP 0.0.3 Documentation
63
63
  require_paths:
64
64
  - lib
65
65
  required_ruby_version: !ruby/object:Gem::Requirement