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.
- data/lib/katcp/client/roach.rb +195 -32
- data/lib/katcp/client.rb +1 -1
- data/lib/katcp/response.rb +5 -18
- data/lib/katcp/util.rb +5 -21
- data/lib/katcp/version.rb +1 -1
- metadata +4 -4
data/lib/katcp/client/roach.rb
CHANGED
@@ -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.
|
8
|
-
#
|
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
|
-
#
|
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) ->
|
57
|
-
# read(register_name,
|
58
|
-
# read(register_name,
|
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
|
-
#
|
61
|
-
#
|
62
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
#
|
116
|
-
#
|
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
|
-
#
|
128
|
-
|
129
|
-
|
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
|
-
|
133
|
-
|
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]
|
data/lib/katcp/response.rb
CHANGED
@@ -162,26 +162,13 @@ module KATCP
|
|
162
162
|
end
|
163
163
|
|
164
164
|
# call-seq:
|
165
|
-
# payload -> String
|
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
|
173
|
-
# false or no payload exists.
|
174
|
-
|
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,
|
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
|
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
|
-
|
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
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
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-
|
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.
|
62
|
+
- Ruby/KATCP 0.0.3 Documentation
|
63
63
|
require_paths:
|
64
64
|
- lib
|
65
65
|
required_ruby_version: !ruby/object:Gem::Requirement
|