Class: KATCP::RoachClient
Overview
Facilitates talking to tcpborphserver2
, a KATCP server implementation that runs on ROACH boards. In addition to providing convenience wrappers around tcpborphserver2
requests, it also adds the following features:
-
Hash-like access to read and write gateware devices (e.g. software registers, shared BRAM, etc.) via methods #[] and #[]=.
-
Each RoachClient instance dynamically adds (and removes) reader and writer attributes (i.e. methods) for gateware devices as the FPGA is programmed (or de-programmed). This not only provides for very clean and readable code, it also provides convenient tab completion in irb for gateware specific device names. Subclasses can exert some control over the dynamic method genreation. See #device_typemap for details.
-
Word based instead of byte based data offsets and counts. KATCP::Client data transfer methods #read and #write deal with byte based offsets and counts. These methods in KATCP::RoachClient deal with word based offsets and counts since ROACH transfers (currently) require word alignment in both offsets and counts. To use byte based offsets and counts explicitly, use
request(:read, ...)
instead ofread(...)
etc.
Constant Summary collapse
- DEVICE_TYPEMAP =
This is the default (empty) typemap. It exists here so that subclasses (and their subclasses) have the option of using the following idiom to create their own custom typemap that includes their superclass’s typemap:
class SomeClass < KATCP::RoachClient DEVICE_TYPEMAP = superclass::DEVICE_TYPEMAP.merge({ :some_device => :rwreg }) ... end class MyClass < SomeClass DEVICE_TYPEMAP = superclass::DEVICE_TYPEMAP.merge({ :switch_gbe_status => :roreg, :switch_gbe => :tenge, :adc_rms_levels => :bram }) ... end
As defined above, MyClass::DEVICE_TYPEMAP will be:
{ :some_device => :rwreg, :switch_gbe_status => :roreg, :switch_gbe => :tenge, :adc_rms_levels => :bram }
Because the superclass of SomeClass is KATCP::RoachClient, the “superclass::DEVICE_TYPEMAP.merge” part is optional in SomeClass, but it is still recommended since future versions of KATCP::RoachClient may have a non-empty typemap.
{}
- PROGDEV_SOCKET_TIMEOUT =
This is the default timeout to use when programming a bitstream via #progdev.
5
Constants inherited from Client
Client::DEFAULT_SOCKET_TIMEOUT
Instance Attribute Summary collapse
-
#devices ⇒ Object
readonly
Returns an Array of Strings representing the device names from the current design.
Instance Method Summary collapse
-
#bulkread(register_name, *args) ⇒ Object
call-seq: bulkread(register_name) -> Integer bulkread(register_name, word_offset) -> Integer bulkread(register_name, word_offset, word_count) -> NArray.int(word_count).
-
#close ⇒ Object
Override KATCP::Client#close to perform subclass specific post-close cleanup.
-
#connect ⇒ Object
Override KATCP::Client#connect to perform subclass specific post-connection setup.
-
#delbof(image_file) ⇒ Object
call-seq: delbof(image_file) -> KATCP::Response.
-
#device_typemap ⇒ Object
Returns the default device typemap Hash (either the one passed to the constructor or an empty Hash).
-
#echotest(ip_address, echo_port, byte_count) ⇒ Object
call-seq: echotest(ip_address, echo_port, byte_count) -> KATCP::Response.
-
#has_device?(device) ⇒ Boolean
(also: #has_key?)
Returns
true
if the current design has a device nameddevice
. -
#initialize(*args) ⇒ RoachClient
constructor
call-seq: RoachClient.new([remote_host, remote_port=7147, local_host=nil, local_port=nil,] opts={}) -> RoachClient.
-
#listbof ⇒ Object
call-seq: listbof -> KATCP::Response.
-
#listdev ⇒ Object
call-seq: listdev -> KATCP::Response.
-
#progdev(*args) ⇒ Object
call-seq: progdev -> KATCP::Response progdev(image_file) -> KATCP::Response.
-
#programmed? ⇒ Boolean
Returns true if currently programmed (specifically, it is equivalent to
request(@fpgastatus).ok?
). -
#read(register_name, *args) ⇒ Object
(also: #wordread, #[])
call-seq: read(register_name) -> Integer read(register_name, word_offset) -> Integer read(register_name, word_offset, word_count) -> NArray.int(word_count).
-
#status ⇒ Object
call-seq: status -> KATCP::Response.
-
#sysinit ⇒ Object
call-seq: sysinit -> KATCP::Response.
-
#tap_start(tap_device, register_name, ip_address, *args) ⇒ Object
call-seq: tap_start(tap_device register_name, ip_address) -> KATCP::Response tap_start(tap_device register_name, ip_address, port) -> KATCP::Response tap_start(tap_device register_name, ip_address, port, mac) -> KATCP::Response.
-
#tap_stop(register_name) ⇒ Object
call-seq: tap_stop(register_name) -> KATCP::Response.
-
#uploadbof(net_port, filename, *args) ⇒ Object
call-seq: uploadbof(net_port, filename) -> KATCP::Response uploadbof(net_port, filename, size) -> KATCP::Response.
-
#write(register_name, *args) ⇒ Object
(also: #wordwrite, #[]=)
call-seq: write(register_name, data) -> self write(register_name, word_offset, data) -> self.
Methods inherited from Client
#client_list, #configure, #connected?, #halt, #help, #host, #informs, #inspect, #log_level, #method_missing, #mode, #port, #request, #restart, #sensor_dump, #sensor_list, #sensor_sampling, #sensor_value, #to_s, #watchdog
Constructor Details
#initialize(*args) ⇒ RoachClient
call-seq: RoachClient.new([remote_host, remote_port=7147, local_host=nil, local_port=nil,] opts={}) -> RoachClient
Creates a RoachClient that connects to a KATCP server at remote_host
on remote_port
. If local_host
and local_port
are specified, then those parameters are used on the local end to establish the connection. Positional parameters can be used OR parameters can be passed via the opts
Hash.
Supported keys for the opts
Hash are:
:remote_host Specifies hostname of KATCP server
:remote_port Specifies port used by KATCP server
(default ENV['KATCP_PORT'] || 7147)
:local_host Specifies local interface to bind to (default nil)
:local_port Specifies local port to bind to (default nil)
:socket_timeout Specifies timeout for socket operations
(default DEFAULT_SOCKET_TIMEOUT)
:typemap Provides a way to override the default device typemap
(default {}). See #device_typemap for details.
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/katcp/client/roach.rb', line 216 def initialize(*args) # If final arg is a Hash, pop it off @opts = (Hash === args[-1]) ? args.pop : {} # List of all devices @devices = []; # List of dynamically defined device attrs (readers only, writers implied) @device_attrs = []; # @device objects is a Hash of created device objects: key is Class, # value is a Hash mapping device name to instance of Class. @device_objects = {} # Merge @opts[:typemap] (if given) into device_typemap. # This must be done *before* calling super. device_typemap.merge!(@opts[:typemap]) if @opts[:typemap] # Call super *after* initializing instance variables and possibly # updating typemap. super(*args) end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method in the class KATCP::Client
Instance Attribute Details
#devices ⇒ Object (readonly)
Returns an Array of Strings representing the device names from the current design. The Array will be empty if the currently programmed gateware has no devices (very rare, if even possible) or if no gateware is currently programmed.
195 196 197 |
# File 'lib/katcp/client/roach.rb', line 195 def devices @devices end |
Instance Method Details
#bulkread(register_name, *args) ⇒ Object
call-seq:
bulkread(register_name) -> Integer
bulkread(register_name, word_offset) -> Integer
bulkread(register_name, word_offset, word_count) -> NArray.int(word_count)
Reads a word_count
words starting at word_offset
offset from register (or block RAM) named by register_name
. Returns an Integer unless word_count
is given in which case it returns an NArray.int(word_count).
Equivalent to #read (but uses a bulkread request rather than a read request if bulkread is supported).
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 |
# File 'lib/katcp/client/roach.rb', line 551 def bulkread(register_name, *args) # Defer to #read unless server provides bulkread command return read(register_name, *args) unless @bulkread == :bulkread byte_offset = 4 * (args[0] || 0) byte_count = 4 * (args[1] || 1) raise 'word count must be non-negative' if byte_count < 0 resp = request(:bulkread, register_name, byte_offset, byte_count) raise resp.to_s unless resp.ok? data = resp.lines[0..-2].map{|l| l[1]}.join if args.length <= 1 || args[1] == 1 data.unpack('N')[0] else data.to_na(NArray::INT).ntoh end end |
#close ⇒ Object
Override KATCP::Client#close to perform subclass specific post-close cleanup. Be sure to call super afterwards!
252 253 254 255 256 257 |
# File 'lib/katcp/client/roach.rb', line 252 def close # Undefine device-specific attributes (if device is programmed) undefine_device_attrs ensure super end |
#connect ⇒ Object
Override KATCP::Client#connect to perform subclass specific post-connection setup.
237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/katcp/client/roach.rb', line 237 def connect super # Determine which commands are supported by the server commands = request(:help).to_s # Determine whether bulkread is used by server @bulkread = commands.index('#help bulkread') ? :bulkread : :read # Determine whether status or fpgastatus command is used by server @fpgastatus = commands.index('#help fpgastatus') ? :fpgastatus : :status # Define device-specific attributes (if device is programmed) define_device_attrs self end |
#delbof(image_file) ⇒ Object
call-seq:
delbof(image_file) -> KATCP::Response
Deletes gateware image file named by image_file
.
572 573 574 |
# File 'lib/katcp/client/roach.rb', line 572 def delbof(image_file) request(:delbof, image_file) end |
#device_typemap ⇒ Object
Returns the default device typemap Hash (either the one passed to the constructor or an empty Hash). Design specific subclasses can override this method to return a design specific device typemap.
This method’s return value controls how methods and aliases are dynamically generated for devices within the ROACH gateware. If #device_typemap returns nil
or an empty Hash, all devices will be treated as read/write registers. Otherwise, #device_typemap must return a Hash-like object. If the object returned by #device_typemap contains a key (String or Symbol) for a given device name, the key’s corresponding value specifies how to treat that device when dynamically generating accessor methods for it and whether to generate any aliases for it. If no key exists for a given device, the device will be treated as a read/write register. The corresponding value can be one of:
:roreg (Read-only register) Only a reader method will be created.
:rwreg (Read-write register) Both reader and writer methods will be
created.
:bram (Shared BRAM) A reader method returning a Bram object
will be created. The returned Bram object
provides convenient ways to read and write
to the Bram device.
:tenge (10 GbE) A reader method returning a TenGE object
will be created. The returned TenGE object
provides convenient ways to read and write
to the TenGE device.
:snap (Snapshot) A reader method returning a Snapshot object
will be created. The returned Snapshot
object provides a trigger method and acts
like a Bram object for the Snapshot's
memory element. Must be used with the
snapshot block's BRAM (or DRAM) device.
:qdrctrl (QDR controller) A reader method returning a QdrCtrl object
will be created. The returned QdrCtrl
object provides methods to reset the QDR
controller and check its cal_fail and
phy_rdy status bits.
:skip (unwanted device) No method will be created.
A class name (custom) A user-supplied class can be given to
allow for customized device access.
If a class name is specified, the method defined for the corresponding device will return an instance of the given class. The constructor will be passed the KATCP::Client instance and a String specifying the device name. Here is an example of a suitable class definition:
class MyDevice
def initialize(katcp_client, device_name)
# Save client and device name for future use
@katcp_client = katcp_client
@device_name = device_name
end
# Other functionality defined here
end # class MyDevice
Methods are only created for devices that actually exist on the device. If no device exists for a given key, no methods will be created for that key. In other words, regardless of the keys given, methods will not be created unless they are backed by an actual device. Both reader and writer methods are created for devices for which no key is present.
The value can also be an Array whose first element is a Symbol (or class name) from the list above. The remaining elements specify aliases to be created for the given attribute methods.
RoachClient#device_typemap returns on empty Hash so all devices are treated as read/write registers by default. Gateware specific subclasses of RoachClient can override #device_typemap method to return a object containing a Hash tailored to a specific gateware design.
Example: The following would lead to the creation of the following methods and aliases: “input_selector”, “input_selector=”, “insel”, “insel=”, “switch_gbe_status”, “switch_gbe”, “adc_rms_levels”, and “my_device” (assuming the named devices all exist!). No methods would be created for the device named “unwanted_reg” even if it exists.
class MyRoachDesign < RoachClient
DEVICE_TYPEMAP = superclass::DEVICE_TYPEMAP.merge({
:input_selector => [:rwreg, :insel],
:switch_gbe_status => :roreg,
:switch_gbe => :tenge,
:adc_rms_levels => :bram,
:my_device => MyDevice,
:unwanted_reg => :skip
})
def device_typemap
@device_typemap ||= DEVICE_TYPEMAP.dup
end
end
If the user passes a typemap Hash to the constructor, that Hash is merged into the Hash returned by device_typemap. This can have side effects that might be unwanted if device_typemap returns a Hash that is referenced by a class constant. To avoid that, it is recommended that device_typemap return the instance variable @device_typemap that is lazily initialized with a copy of the class constant as shown above.
452 453 454 |
# File 'lib/katcp/client/roach.rb', line 452 def device_typemap @device_typemap ||= DEVICE_TYPEMAP.dup end |
#echotest(ip_address, echo_port, byte_count) ⇒ Object
call-seq: echotest(ip_address, echo_port, byte_count) -> KATCP::Response
Basic network echo tester.
580 581 582 |
# File 'lib/katcp/client/roach.rb', line 580 def echotest(ip_address, echo_port, byte_count) request(:echotest, ip_address, echo_port, byte_count) end |
#has_device?(device) ⇒ Boolean Also known as: has_key?
Returns true
if the current design has a device named device
.
532 533 534 |
# File 'lib/katcp/client/roach.rb', line 532 def has_device?(device) @devices.include?(device.to_s) end |
#listbof ⇒ Object
call-seq:
listbof -> KATCP::Response
Lists available gateware images.
588 589 590 |
# File 'lib/katcp/client/roach.rb', line 588 def listbof request(:listbof).sort! end |
#listdev ⇒ Object
call-seq:
listdev -> KATCP::Response
Lists available registers.
596 597 598 |
# File 'lib/katcp/client/roach.rb', line 596 def listdev request(:listdev, :size).sort! end |
#progdev(*args) ⇒ Object
call-seq:
progdev -> KATCP::Response
progdev(image_file) -> KATCP::Response
Programs a gateware image specified by image_file
. If image_file
is omitted or nil, de-programs the FPGA.
Whenever the FPGA is programmed, reader and writer attributes (i.e. methods) are defined for every device listed by #listdev except for device names that conflict with an already existing method names.
Whenever the FPGA is de-programmed (or re-programmed), existing attributes that were dynamically defined for the previous design are removed.
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 |
# File 'lib/katcp/client/roach.rb', line 618 def progdev(*args) # Clear args (i.e. remove all elements) if first element is nil. # This also clears args if it is empty, but that is OK since it is # essentially a no-op. Any time saved by not clearing an empty args # would be lost by checking whether args is already empty! :-) args.clear if args[0].nil? prev_socket_timeout = @socket_timeout begin # Adjust @socket_timeout if programming a bitstream @socket_timeout = PROGDEV_SOCKET_TIMEOUT if args[0] resp = request(:progdev, *args) ensure @socket_timeout = prev_socket_timeout end define_device_attrs resp end |
#programmed? ⇒ Boolean
Returns true if currently programmed (specifically, it is equivalent to request(@fpgastatus).ok?
). Older versions of tcpborphserver used the “status” command, while newer versions use the “fpgastatus” command for the same purpose. The @connect method checks which is used by the server and sets @fpgastatus accordingly.
641 642 643 |
# File 'lib/katcp/client/roach.rb', line 641 def programmed? status.ok? end |
#read(register_name, *args) ⇒ Object Also known as: wordread, []
call-seq:
read(register_name) -> Integer
read(register_name, word_offset) -> Integer
read(register_name, word_offset, word_count) -> NArray.int(word_count)
Reads one or word_count
words starting at word_offset
offset from register (or block RAM) named by register_name
. Returns an Integer unless word_count
is given in which case it returns an NArray.int(word_count).
Note that KATCP::Client#read deals with byte based offsets and counts, but all reads on the ROACH must be word aligned and an integer number of words long, so KATCP::RoachClient#read deals with word based offsets and counts.
659 660 661 662 663 664 665 666 667 668 669 670 671 |
# File 'lib/katcp/client/roach.rb', line 659 def read(register_name, *args) byte_offset = 4 * (args[0] || 0) byte_count = 4 * (args[1] || 1) raise 'word count must be non-negative' if byte_count < 0 resp = request(:read, register_name, byte_offset, byte_count) raise resp.to_s unless resp.ok? data = resp.payload if args.length <= 1 || args[1] == 1 data.unpack('N')[0] else data.to_na(NArray::INT).ntoh end end |
#status ⇒ Object
call-seq:
status -> KATCP::Response
Reports if gateware has been programmed.
680 681 682 |
# File 'lib/katcp/client/roach.rb', line 680 def status request(@fpgastatus||:status) end |
#sysinit ⇒ Object
call-seq:
sysinit -> KATCP::Response
Writes the timing ctl register that resets the entire system.
- Presumably this depends on a certain register naming convention?
690 691 692 |
# File 'lib/katcp/client/roach.rb', line 690 def sysinit request(:sysinit) end |
#tap_start(tap_device, register_name, ip_address, *args) ⇒ Object
call-seq:
tap_start(tap_device register_name, ip_address) -> KATCP::Response
tap_start(tap_device register_name, ip_address, port) -> KATCP::Response
tap_start(tap_device register_name, ip_address, port, mac) -> KATCP::Response
Start a tgtap instance.
700 701 702 |
# File 'lib/katcp/client/roach.rb', line 700 def tap_start(tap_device, register_name, ip_address, *args) request(:tap_start, tap_device, register_name, ip_address, *args) end |
#tap_stop(register_name) ⇒ Object
call-seq:
tap_stop(register_name) -> KATCP::Response
Stop a tgtap instance.
708 709 710 |
# File 'lib/katcp/client/roach.rb', line 708 def tap_stop(register_name) request(:tap_stop, register_name) end |
#uploadbof(net_port, filename, *args) ⇒ Object
call-seq:
uploadbof(net_port, filename) -> KATCP::Response
uploadbof(net_port, filename, size) -> KATCP::Response
Upload a gateware image.
NOT YET IMPLEMENTED
719 720 721 |
# File 'lib/katcp/client/roach.rb', line 719 def uploadbof(net_port, filename, *args) raise NotImplementedError.new('uploadbof not yet implemented') end |
#write(register_name, *args) ⇒ Object Also known as: wordwrite, []=
call-seq:
write(register_name, data) -> self
write(register_name, word_offset, data) -> self
Write data
to word_offset
(0 if not given) in register named register_name
. The data
argument can be a String containing raw bytes (byte length must be multiple of 4), NArray.int, Array of integer values, or other object that responds to #to_i.
731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 |
# File 'lib/katcp/client/roach.rb', line 731 def write(register_name, *args) word_offset = (args.length > 1) ? args.shift : 0 byte_offset = 4 * word_offset args.flatten! args.map! do |a| case a when String; a when NArray; a.hton.to_s when Array; a.pack('N*') else [a.to_i].pack('N*') end end data = args.join byte_count = data.length if byte_count % 4 != 0 raise "data length of #{byte_count} bytes is not a multiple of 4 bytes" elsif byte_count == 0 warn "writing 0 bytes to #{register_name}" end resp = request(:write, register_name, byte_offset, data, byte_count) raise resp.to_s unless resp.ok? self end |