katcp 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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