Hello AFU – Part 3

This is part 3 of my Hello AFU tutorial. In the last part we built components to handle the AFU reset. In this part we’ll look at the requests coming in for the AFU descriptor and build a mechanism to send this data back to the PSL.

svAFU Ports

Before we get started here, I noticed something odd that’s good to be aware of. In the first post I commented out the structured inputs and outputs because Quartus was throwing errors that they were not defined. I assumed it didn’t like that they weren’t being used, but after re-cloning the repo down it started to give me those errors again.

It looks like this error might be just related to some of the order the components are being built in, if you comment out all the structured inputs it’ll synthesize the project successfully. After that, you can uncomment it all and it will synthesize just fine. If anyone has insight into why this is happening or a better fix, I’d appreciate any feedback you have to offer.



AFU Descriptor Read Requests

After the AFU handles its initial reset signal, the next batch of signals are requests over the MMIO interface for the AFU descriptor. The AFU decriptor provides some details about the AFUs function and setup.

To add the MMIO interfaces to the wave viewer, I’ll add a do watch_mmio_interface.do line to my test.do script before the run 40 command.

The MMIO operations are synchronous so the PSL will send a single request and wait until it gets a response. We can see the first signal coming in here:

first_mmio

Like with the job interface, the ha_mmval will be raised when a valid command is active. The ha_mmcfg being high lets us know this a request for data in the AFU descriptor. ha_mmrnw is high for read requests and low for write requests. ha_mmdw is low for 32-bit requests and high for 64-bit. ha_mmad is the address of the data being requested, and ha_mmadpar is the odd parity bit for that address. ha_data and ha_datapar are only used for write requests, so we don’t need to look at those quite yet.

Parameterizing the Shift Register

Before we can send data back, we need to make a modification to our shift register. Similar to our previous jdone signal we shifted back a clock cycle, we need to do the same here for ah_mmack and ah_mmdata. Our shift register as-is will work fine for ah_mmack, as it’s also a signal signal. For ah_mmdata we need it to be 64 bits wide to support the whole bus being shifted back a clock cycle.

SystemVerilog provides as a construct to parameterize a module, allowing us to modify some of how it’s operating on a per-instance basis. In this case I want to add a width field that lets us set the width the bus.

The logic in the always_ff block does not need to change for this, we just need to define the parameter and use it in the input and output port declarations

module shift_register #(parameter width = 1) (
  input logic clock,
  input logic [0:width-1] in,
  output logic [0:width-1] out);

In this change, we now have a default width of 1, so that we don’t need to change the shift registers already in use. For the module we’re about to build we can now create an instance like this for a 64 bit wide shifter:

shift_register #(64) data_shift(
  .clock(clock),
  .in(data),
  .out(mmio_out.data));

Handling AFU Descriptor requests

I will define and add a new file mmio.sv to the project that will be responsible for all MMIO request handling. It will have some internal variables ack and data to hold the data that will be shifted back. Additionally it will have some logic to set the ah_mmdatapar bit. That parity bit doesn’t need to be shifted because we can hook it up to the current output to save a couple logic gates.

import CAPI::*;

module mmio (
  input logic clock,
  input MMIOInterfaceInput mmio_in,
  output MMIOInterfaceOutput mmio_out);

  logic ack;
  logic [0:63] data;

  shift_register ack_shift(
    .clock(clock),
    .in(ack),
    .out(mmio_out.ack));

  shift_register #(64) data_shift(
    .clock(clock),
    .in(data),
    .out(mmio_out.data));

  // Set parity bit for MMIO output
  assign mmio_out.data_parity = ~^mmio_out.data;

  always_ff @(posedge clock) begin
    if(mmio_in.valid) begin
      if(mmio_in.cfg) begin
        if(mmio_in.read) begin
          ack <= 1;
          data <= 1;
        end
      end
    end else begin
      ack <= 0;
      data <= 0;
    end
  end

endmodule

For now, I’m not as worried about sending proper data as I am getting all the pieces laid out and working. I’ll add an instance of this new mmio module in my parity_afu module.

mmio mmio_handler(
  .clock(clock),
  .mmio_in(mmio_in),
  .mmio_out(mmio_out));

Looking at the waves now, we can see 7 MMIO requests coming in, and for each we’re sending back a simple 1 across on the data bus.

first_mmio_writes

Since we didn’t send a proper descriptor, PSLSE complains ERROR:AFU descriptor num_of_processes=0!

Either way it’s starting to come together so I’ll commit my changes and move on.

Defining a New Type

It took me a while to find a way to handle these AFU requests that I felt was functional and cleanly coded. Most AFU descriptor implementations I’ve seen so far are using some verilog implementation of ROM, and this is how I first implemented this.

I found this method to be a bit cumbersome, so I decided to extend my capi.sv to include a new structure definition for an AFU descriptor. This format is modeled after whats described in the CAPI User’s Manual.

  typedef struct packed {
    bit [0:15] num_ints_per_process;
    bit [0:15] num_of_processes;
    bit [0:15] num_of_afu_crs;
    bit [0:15] req_prog_model;
    bit [0:199] reserved_1;
    bit [0:55] afu_cr_len;
    bit [0:63] afu_cr_offset;
    bit [0:5] reserved_2;
    bit psa_per_process_required;
    bit psa_required;
    bit [0:55] psa_length;
    bit [0:63] psa_offset;
    bit [0:7] reserved_3;
    bit [0:55] afu_eb_len;
    bit [0:63] afu_eb_offset;
  } AFUDescriptor;

To support reading the right portions of the AFU descriptor, a SystemVerilog function felt like the best route. This initial implementation is built just to support the regions of the AFU descriptor that I’ve seen requests come in to so far.

function bit [0:63] read_afu_descriptor(AFUDescriptor descriptor,
                                        bit [0:23] address);
  case(address)
    'h0: begin
      return {descriptor.num_ints_per_process,
              descriptor.num_of_processes,
              descriptor.num_of_afu_crs,
              descriptor.req_prog_model};
    end
    default: begin
      return 0;
    end
  endcase
endfunction

With this new type and function to help reading it added to my CAPI package, I can create an instance of this type in my mmio module and set the values appropriately.

  AFUDescriptor afu_desc;

  assign afu_desc.num_ints_per_process = 0,
         afu_desc.num_of_processes = 1,
         afu_desc.num_of_afu_crs = 0,
         afu_desc.req_prog_model = 16'h8010,
         afu_desc.reserved_1 = 0,
         afu_desc.afu_cr_len = 0,
         afu_desc.afu_cr_offset = 0,
         afu_desc.reserved_2 = 0,
         afu_desc.psa_per_process_required = 0,
         afu_desc.psa_required = 0,
         afu_desc.psa_length = 0,
         afu_desc.psa_offset = 0,
         afu_desc.reserved_3 = 0,
         afu_desc.afu_eb_len = 0,
         afu_desc.afu_eb_offset = 0;

The last step is to replace our hard-coded response with the newly defined function.

data <= read_afu_descriptor(afu_desc, mmio_in.address);

With that completed, I’ll verify I’m getting the expected behavior during simulation.

afu_mmio

Now that we’ve returned enough AFU data the PSLSE output shows us we’re ready to connect a client!

INFO:PSLSE version 1.002 compiled @ Feb  5 2016 11:47:34
INFO:PSLSE parm values:
    Seed     = 13
    Timeout  = 10 seconds
    Response = 16%
    Paged    = 3%
    Reorder  = 86%
    Buffer   = 82%
INFO:Attempting to connect AFU: afu0.0 @ localhost:32768
PSL_SOCKET: Using PSL protocol level : 0.9908.0
INFO:Clocking afu0.0
WARNING:ah_brlat must be either 1 or 3!
WARNING:ah_brlat must be either 1 or 3!
INFO:Started PSLSE server, listening on kbawx:16384

There are also a couple of warnings about the buffer read latency, but I’ll wait to address that when we look at using the buffer interface. With this bit implemented, I’ll commit my changes and in the next post we’ll look at communicating with our AFU from userspace.

Hello AFU – Part 2

This is the second part of my Hello AFU tutorial. In the last part we setup the base project and wrote a few scripts that will help view signals in ModelSim.

In this part we’ll look at the signals received by the AFU from the PSL and implement the reset handling required by all AFUs.

Resetting the AFU

The first thing the PSL requests of the AFU is to reset to a known good state. This is very easy to implement at this point as we have no internal state! Let’s look at the signals coming in for this.

reset_command

There are two implemented job control commands documented in the CAPI User’s Manual: START(0x90) and RESET(0x80). Initially and between jobs, the RESET command is sent to the AFU. The expectation on the AFU when given a RESET command is that it will reset its internal state, then raise the ah_jdone signal for a cycle.

Tip: All signals that begin with ah_ represent signals from the Accelerator to Host, signals starting with ha_ represent from the Host to the Accelerator

The purpose for each signal is documented within the CAPI User’s Manual, for the RESET command only the ha_jval and ha_jcom are important on the receiving side. ha_jcompar should also be properly set to set to the odd parity bit.

Before we handle this signal, let’s fix all the floating signals we’re sending back. They are floating because we aren’t explicitly setting these signals high or low, so lets set them all low. I’ll set the timebase_request and parity_enabled signals low while I’m at it. The signal names are different in my code as I’ve given them more verbose names in the capi.sv package file used to abstract these out. I’ve added this to my parity_afu module definition just above the always_ff statement.

assign job_out.running = 0,
       job_out.done = 0,
       job_out.cack = 0,
       job_out.error = 0,
       job_out.yield = 0,
       timebase_request = 0,
       parity_enabled = 0;

Additionally, I will need to uncomment the portion of afu.sv that routes these signals into the AFU.

Also, before testing this in the simulator, I write one more do file test.do to make it easier to see what I’m working on in simulation. It will prepare simulation, watch the job interface, then run for 10 cycles.

vsim work.top
do watch_job_interface.do
run 40

With those changes made I’ll verify the signals are now being driven low.

low_signals



Driving signals

There two main ways to drive signals in SystemVerilog, blocking = and non-blocking <=. These can be confusing terms, this page can help clear it up a little bit. Until you’re comfortable with the difference I suggest you use = only in assign statements where you are driving a signal to a constant value as we are so far. When you use <=, the value will stick in a register, preserving it’s value until changed later.

To send our ah_jdone signal, we need to detect when the ha_jval is high combined with a ha_jcom set to RESET, then we’ll know it’s appropriate to raise the ah_jdone signal.

For signals that we want to change during a clock cycle, we can put non-blocking assignments in an always_ff block. We first need to remove the blocking assignment made with the assign command as only one driver can be used to set the signal. Next we’ll add an if statement that will drive the done signal high only if a valid reset command is given, we also need to set it low in all other conditions so we’ll use an else statement to ensure we get that behavior.

always_ff @(posedge clock) begin
  if(job_in.valid & job_in.command == RESET) begin
    job_out.done <= 1;
  end else begin
    job_out.done <= 0;
  end
end

done_wrong

I’ve been advised that there is one thing wrong with this design, the done signal should be sent on the next clock cycle. We need something to help us delay the signal.

Making a shift register

There are a few ways to do this, I’ve elected to use a shift register to fulfill this need.

This shift register will pass its input to its output, delaying changes by a single clock cycle. It’s not the most useful shift register but it will do for this purpose.

module shift_register (
  input logic clock,
  input logic in,
  output logic out);

  always_ff @ (posedge clock) begin
    out <= in;
  end
endmodule

To use this module in our parity_afu module, we’ll need to create an instance of our shift_register module and a variable to reference its input. In the instance we create we’ll reference the inputs and outputs, then change our job logic to use the new jdone variable instead of the direct output.

logic jdone;

shift_register jdone_shift(
  .clock(clock),
  .in(jdone),
  .out(job_out.done));

Once this is all said and done, we should get the output shifted back a cycle as we desired. Since the shift register is setup with our internal jdone as input and the job_out.done as output, this will affect all assignments to jdone.

done_right

See these changes committed here.

This should be sufficient to handle the reset command for now. The next set of signals our AFU will receive will be requests for the AFU descriptor over the MMIO interface, I’ll walk through implementing this in my next post.

Hello AFU – Part 1

These posts describe the process I’ve followed to build an AFU (Accelerator Function Unit) for use in CAPI development. I’ve only been tinkering with RTL design for a few weeks, so this may not be the ideal design flow but it has worked for me so far!

Tools and Setup

My development setup basically follows what I described in my Tinkering wtih CAPI post, though I have switched to using the Gnome window manager as some software I was using didn’t have as good support for XFCE. Additionally, my 30 day evaluation for Quartus Prime was close to expiring so I’m now using the free Lite Edition.

Since I’ve started this digital adventure I’ve come to appreciate some of the nice features SystemVerilog adds to the design flow. As a developer more familiar with userspace programming, the addition of packages, enums, structures and type-definitions are very attractive and I cannot resist using them! I have found that not all design software supports all SystemVerilog semantics but I will use as much as I can get away with because they are very handy.

The design I will go through is something I’ve already put together and is available on Github. I will pull in some files from this repo when they are handy, but step through my process as if this were an external resource and find some areas for improvement along the way.



Parity Generator AFU

The overall goal of this AFU is to practice making a fully-functioning AFU that is paired up with a client application that utilizes it. The goal is to take two memory buffers in an application and generate parity for those buffers using XOR. We’ll start with a nearly clean slate and build our way up to this goal.

Getting the Project Started

As with the Tinkering wtih CAPI post, I start by creating a new empty project using top as the top-level design entry; this time I will name my project and it’s folder hello-afu.

Next, I copy top.v from the PSLSE project into the root directory of my project.

Additionally, I will pull in some helper files from my capi-parity example project. I wrote capi.sv to organize and simplify some of the interfaces the PSL exposes to the AFU and afu.sv to map signals to that abstraction for use in SystemVerilog.

These files don’t get automatically imported into the project, so I’ll add them via Project->Add/Remove Files in Project. The Add All button in this window picks up all the Verilog and SystemVerilog in the project directory.

add_files_to_project

At this point I tried to synthesize the project, and got the error Node instance "svAFU" instantiates undefined entity "parity_afu". since my afu.sv references a module that does not exist yet.

You can use File->New to create a new SystemVerilog design file. In this file, I import my CAPI package and define the input and output ports for the module. The only functionality is a debug message to notify me of the rising clock edge.

import CAPI::*;

module parity_afu (
  input clock,
  output timebase_request,
  output parity_enabled,
  input JobInterfaceInput job_in,
  output JobInterfaceOutput job_out,
  input CommandInterfaceInput command_in,
  output CommandInterfaceOutput command_out,
  input BufferInterfaceInput buffer_in,
  output BufferInterfaceOutput buffer_out,
  input ResponseInterface response,
  input MMIOInterfaceInput mmio_in,
  output MMIOInterfaceOutput mmio_out);

  always_ff @(posedge clock) begin
    $display("Clock edge!");
  end

endmodule

Again I try to synthesize the project, this time getting a smattering of messages along the lines of Port "buffer_in" does not exist in the macrofunction "svAFU". I suppose Quartus Prime does not like that I’m not yet using these inputs, so I opened the afu.sv file and commented out the ports that are defined but unused structures, it doesn’t seem to be as picky about the inputs and outputs that are not structures.

At this stage is have a useless AFU that will at least synthesize, I prepare my project for version control and push my initial commit to github.



Meeting ModelSim

Tip: I find it inconvenient that I need to edit the simulation/modelsim/modelsim.ini to reference the PSLSE veriuser.sl each time I open up ModelSim. So I’ve elected to modify my /home/kwilke/altera_lite/15.1/modelsim_ase/modelsim.ini file so that I don’t have to set this manually and remember to compile options in the ModelSim interface. This will effect all of my projects, but since I’m only using this for working with PSLSE that is something I find acceptable.

My next step is to begin simulating to see how my empty AFU is faring so far. Find the button for RTL Simulation or you can go to Tools->Run Simulation->RTL Simulation to open ModelSim.

Since my last article, I’ve learned a few tricks to make my life with ModelSim easier. Most of the interactions within the interface correspond to commands ran in the VSIM prompt at the bottom of the main interface. Instead of going to Simulate->Start Simulation and picking the same options every time, I can just run vsim work.top in the command line. Even better, you can combine these commands into DO files that help automate some of the things you want the simulator to… do.

After entering a vsim work.top command the simulator is ready to go. The AFU as written so far does only one thing, which is to display an output in the simulator to notify us of the rising edge of the clock signal. Since the PSL provides a 250Mhz clock, the phase time for this is 4 nanoseconds. We can use run 4 to simulate 4 nanoseconds of work. As before with the memcopy example, ModelSim will hang a bit as it’s waiting for a connection to PSLSE. Once started, the output will be something like below:

# AFU Server is waiting for connection on kbawx:32768
# PSL client connection from localhost
# Using PSL protocol level : 0.9908.0
# Clock edge!

Text is great for some events, but something more useful is a visual timeline of the signals as they are changing. Let’s watch the clock signal itself.

In the sim window near the upper left part of the interface, you can see a hierarchy of instances, drill down into this window and select our svAFU from within top->a0->svAFU.

svafu_in_sim

After selecting svAFU the Objects window on the right side will contain a list of the objects within that instance. We can find the clock signal as it exists coming into our parity_afu module. With clock selected, you can right click and select Add Wave or use CTRL+w to open it in ModelSim’s waveform viewer. The command add wave -position insertpoint sim:/top/a0/svAFU/clock can been seen in the console, providing you the syntax for the command to watch a waveform.

clock_in_objects

Running the simulation for a few more phases we can see the clock signal is now being recorded.

Tip: There are buttons in the interface to run the simulation for different amount of times, by default the run length is set to 100ns. In the picture below I’ve changed this to 4ns and used the button just to the right to run it for that amount of time.

clock_signal

To help scripting these commands, you can start to type a command in the vsim prompt, and it will show you the available flags and options.

wave_autocomplete

I have a few scripts in my capi-parity project that you can put in the simulation/modelsim/ directory within your project that will add the various PSL signals. This script will set the radix for many signals to something more useful than binary and change the color of AFU outputs to yellow to help differentiate what’s coming in and out of your AFU. Once a do file is in your simulation/modelsim/ directory you can run it like do watch_all_interfaces.do. Tab completion is supported to ease that process as well.

With these scripts added, I commit my repo changes as I want to share and take them with me if I work on this project from another workstation.

I feel this covers a good about for a single post, in the next post we’ll start looking at what needs to be implemented to get this AFU to function in the most basic sense.