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.
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.
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
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
.
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.