Class: KATCP::RoachClient

Inherits:
Client
  • Object
show all
Defined in:
lib/katcp/client/roach.rb

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 of read(...) 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

Instance Method Summary collapse

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

#devicesObject (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

#closeObject

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

#connectObject

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_typemapObject

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.

Returns:

  • (Boolean)


532
533
534
# File 'lib/katcp/client/roach.rb', line 532

def has_device?(device)
  @devices.include?(device.to_s)
end

#listbofObject

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

#listdevObject

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.

Returns:

  • (Boolean)


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

#statusObject

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

#sysinitObject

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

Raises:

  • (NotImplementedError)


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