Locally resetting an AXI component
Earlier this week, I came across an AXI component designed by a user who was struggling to figure out why his component wasn’t working. In the end the first answer to why his core wasn’t working was that he was neither simulating, nor formally verifying his design. The second answer was that he had a clock domain crossing issue that wasn’t part of his post.
Looking through his logic, however, I found a third reason why he might be struggling–his reset logic. This reset code contained the following logic,
The design also contained an associated slave, also containing a
LOCAL_RESET
wire, although none of the logic within the slave
depended upon the same LOCAL_RESET
.
It shouldn’t be too hard to come up with a scenario that will cause such
a design to read results into the wrong address, or worse to hang the rest
of the design by dropping axi_rready
just before M_AXI_RVALID
gets
set.
Although this didn’t turn out to be what the user was
struggling from in the end, it still brings up an interesting question: How
shall an AXI component be locally reset? By locally reset, I’m referring
to resetting the component without pulling S_AXI_ARESETN
low and resetting
every component on the
bus.
It’s not as simple as it might sound.
Locally resetting an AXI Slave component
Resetting an AXI Slave is the easier type of component to reset. The key to resetting a slave with an AXI-lite interface is to reset the registers within the design, rather than the entire design, as shown in Fig. 1 on the right.
As an example, in our example AXI slave design, we included the following logic for updating an register in a design having an AXI-lite interface:
This register logic is the only logic that needs to adjust in order to implement a local reset for an AXI-Lite slave.
First, we’d need a reset signal from somewhere. Such a signal could come from either an external input port (that was synchronous with the design clock),
or it could come from one of the bus-accessible registers within the design,
However you create your local reset signal, once the LOCAL_RESET
signal
is available, the registers within the design can be reset using it.
Notice that I’m not resetting any of the AXI signals, but just the various registers that would be set by them. This is the key to not hanging the bus.
Returning an error during the local reset
If you want, you could also adjust the responses to various requests as well, so that during a reset the bus would return an error. In this case, you’d change,
into something where any write during a LOCAL_RESET
would produce an
error.
The key to this transformation, however, is making certain that the
S_AXI_BRESP
signal still only every changes when the outgoing transaction
isn’t stalled. This is then as simple as,
A similar update to S_AXI_RRESP
could accomplish the same effect there as
well.
The difficult part of this change, however, is that a bus error will tend to cause any user program to crash. Bus error handling is typically done by the operating system and, well, let me ask, if you aren’t using an operating system then is your embedded software ready to handle that challenge?
Retrofitting an existing component
But what if your AXI slave component already
exists? In this case, you
might need to retrofit a slave core with a local reset option. Pulling the
S_AXI_ARESETN
line low of that component only might look like a tempting
option, but what would this do to any ongoing
bus transactions? It would
hang the bus, bringing down
the rest of the design.
This is unacceptable.
However, if this is the sort of functionality you are looking for, just without
hanging the bus, then there is
a way to accomplish it. Simply place an AXI
firewall
between your component and the rest of the
bus,
and then adjust the reset line to the core below the
firewall as
you see fit. Any ongoing slave transaction hiccups will be caught by the AXI
firewall,
and used to prolong the reset of the component core–returning AXI SLVERR
s
(a type of AXI bus
error)
until the core is fully re-integrated back into your system.
An Example
Perhaps an example might clarify this whole idea. I used the register based reset approach when resetting the AXI-Lite version of my Wishbone scope.
If you aren’t familiar with the (now possibly misnamed) Wishbone Scope, it’s a basic bus-based internal logic analyzer. It’s a really easy way to capture, record, and report back to you what’s going on within your design with one caveat: It will only work if the bus never hangs.
This is one of those times and reasons why I like Wishbone: If you just drop the cycle line, then everything resets. I can then go back into a design and (usually) find out what’s going on if I have to.
Not so with AXI: the AXI bus protocol has no bus abort capability. Any misbehaving AXI bus component will hang the bus. This is why it’s so important to formally verify your AXI components–simulation just doesn’t tend to be thorough enough to check all the functionality of an AXI based interaction. Formal methods, on the other hand, will check every possibility.
So how does the reset work in my AXI-Lite scope?
Well, first, it’s not quite so simple as we described above. The scope is designed to be able to (optionally) handle inputs coming from a second clock domain–one associated with the data source. That means that the reset won’t be complete until it has crossed from the bus clock domain to the data clock domain and back again.
So let’s follow this through. There are three parts to the internal reset request logic. First, if a reset has been requested, we clear it on any completion–independent of any new request.
This implements the “clear request” logic of Fig. 3, above and to the right.
Next, if a reset has been requested that hasn’t yet completed (logic above), we keep the request going until it does complete. This would be the case in Fig. 3 between the reset request and the reset acknowledgment.
Finally, the core is responsive to a reset request from the
bus–both from the
bus reset wire
!S_AXI_ARESETN
(called i_reset
below) as well as from a particular write
to the control register. Specifically, if there’s ever a write to the control
register of this
scope,
and the reset bit within that write is low, then we begin a local reset cycle.
Why check for if the reset bit of the control word is low rather than high? It just makes working with the core easier. Normally, writes to the control word contain how long you want to wait from trigger to capture. This requires resetting the core, so the reset request will naturally get set on any such write–even without raising a bit to explicitly request it.
This reset logic then crosses clock
domains via a handshaking
protocol, shown in Fig. 3 above–one we’ve
discussed the basics of before.
It’s not fast, but it works. All of the capture logic is then reset from
a reset signal in the data clock domain, dw_reset
–that’s my equivalent
of the LOCAL_RESET
signal we’ve been discussing above.
Once complete, the return signal, bw_reset_complete
, is set and the
logic above clears. Until the reset completes, any read from the control
register will return with the reset bit set–indicating that the reset
is ongoing. This is useful for those cases where the data clock isn’t
running for some reason. In those cases, if you come back and read from the
scope, using either
the CPU or the debugging
bus), you’ll quickly
notice that the scope
is still in reset–telling you exactly what’s going on.
What about the rest of the bus logic?
It doesn’t change.
The bus still operates like it normally would, unless or until you reset the entire bus, which is just the way we wanted this core to work. The approach is fairly easy to design and easy to implement, and (so far) it’s worked quite well for me.
That handles AXI slave components, but how would you locally reset an AXI master?
Locally resetting an AXI Master component
Resetting an AXI master is a bit more challenging. Basically, the master has to wait for all outstanding transactions to complete in order to complete a reset. This requires something of a state machine.
Let’s walk through the steps of how this might work.
-
First, a reset is requested, the design then transitions from any WORKING state to an INRESET state in order to wait for things to settle out.
-
While in the INRESET state, no additional AXI transactions are initiated. Existing transactions are allowed to complete. This step itself requires a couple of sub-steps.
-
BREADY
andRREADY
need to be set to one if they aren’t alreadyI personally like to keep
BREADY
andRREADY
high any time a transaction is outstanding, and even if no transactions are outstanding. I consider this good practice, although I can imagine reasons why a design might not be able to do this. -
As soon as
!AxVALID || AxREADY
is true on any of the address request channels, theAxVALID
line should to be dropped. That way, no new transaction requests are issued.This assumes that you don’t set
WVALID
high prior to its associatedAWVALID
signal. While the specification allows you to do this, it doesn’t typically buy you any better performance since the interconnect won’t know what to do with the data apart from the address anyway. Still, if you have chosen to allowWVALID
to be set prior toAWVALID
, then you’ll need to make sure to flush theAWVALID
lines as well, just like we’re about to flush theWVALID
signals below. -
As soon as
!WVALID || WREADY
, you will want to setS_AXI_WSTRB
to zero. This will prevent your core from actually writing any more values following the reset request–assuming this was what you wanted.You’ll also want to set
WVALID
to1
as long as you have write transactions outstanding that need to be completed. -
The write channel signaling is a bit trickier. You will need to complete any ongoing write operations. My recommendation would be to set
WVALID
to1
as long as you have an outstanding transaction to complete. This works since you’re settingWSTRB
to zero at the same time, and so you don’t really care any more about what data you are sending, just that the data transactions clear through the system.
Together, these steps will keep you from starting any new transactions.
-
-
While in the
INRESET
state, any and all existing transactions must be allowed to complete.You were keeping track of how many bursts requests you’ve initiated and how many expected responses are still outstanding, right?
As long as transactions remain outstanding, you’ll need to stay in this
INRESET
state. -
Once all transactions have completed, your core may then leave the reset state and return to
IDLE
(orWORKING
–depending upon your core’s design requirements).
Shall we take a look at an example AXI master to see how this might be done?
AXI Master Example
All three of my AXI DMA algorithms have some capability for aborting transactions. This includes my AXI stream to memory core, my AXI memory to stream core, and my AXI DMA core. Unlike the major vendor cores, these three are all open source. Each contains an AXI-Lite control port, and a full memory mapped AXI4 master port.
Since each core contains the same basic reset logic within them, let’s just look at the S2MM core as an arbitrary example. As illustrated in Fig. 5, this core is responsible for accepting stream data, possibly synchronizing to the nearest packet start, and then writing this stream data to memory at a user defined location.
The S2MM core
core has an AXI-lite
interface for control. If
the core
is busy, an abort command can be given to it through this interface which will
cause a form of reset. Such an abort command will be issued if ever the
ABORT_KEY
is written to the upper byte of the control register while the
core is in
operation.
This user-abort signal is then registered, and held high until
the user-commanded, local “reset” is complete and the design returns
to idle (i.e. !r_busy
).
There’s also a similar signal, axi_abort_pending
. This is set not only
based upon the user command, but it also gets set if
the core
ever encounters any bus
errors.
Once either of these signals are set, either cmd_abort
or axi_abort_pending
,
the core
begins a local reset sequence that will return it to its idle state. This
includes aborting all current and ongoing transactions.
First, the core
needs to set BREADY
to one. This particular
core follows my
default philosophy of keeping BREADY
high in the first place,
so no additional action is required there.
Next, AWVALID
needs to be dropped. In this particular
core,
a combinatorial register is used to determine when to set AWVALID
in the
first place. This register is called w_phantom_start
. w_phantom_start
is always cleared in the case of any pending abort.
This signal guarantees that AWVALID
is returned to zero as soon as possible
following any abort request. Notice, in the actual logic block that sets
AWVALID
below, that this signal is only ever changed if
!M_AXI_AWVALID || M_AXI_AWREADY
. This is key to following the protocol and
you’ll see this pattern often.
Clearing WVALID
on a reset takes a bit more work. Or rather, the logic for
WVALID
doesn’t really change. It still gets set at the beginning of every
burst, and remains set until the burst is complete.
Admittedly, this arrangement is somewhat specific to how this stream to
memory core works.
In particular,
this core
only ever sets WVALID
if it has enough information to hold it high until any
burst that it is a part of is complete. This is part of my philosophy of
either using the bus
or getting off the bus
so someone else can use it. While AXI doesn’t require this philosophy, this
design approach helps to minimize any required
bus resources, such as those
used by the interconnect,
and so it helps keep everything else working without getting slowed
down by this core
unless this core
actually needs
the bus to talk to
a peripheral.
The WSTRB
signal similarly straightforward: anytime the
bus
isn’t stalled, WSTRB
is adjusted. If there’s no abort pending, WSTRB
is set to all ones. (As currently written,
the core
can only transfer aligned words of full size. This may change in the future as
I have opportunity.) Otherwise, we stop writing on either user abort or upon
receiving a bus
error as
soon as possible by setting WSTRB
to zero. As before, the
!M_AXI_WVALID || M_AXI_WREADY
condition is key here to following protocol.
The last step is to know when we are done so we can know when to leave this reset state. This means that we need to know when there are no more requests remaining to be made, and when the last outstanding request has been returned. As we’ll see below, the return to idle logic has to be a part of the normal return to idle logic–the task just returns to idle a bit earlier.
Getting there requires checking two counters. You can think of this sort of
like the double hourglass shown in Fig. 6 on the left. Initially, the first
counter is loaded by the user with the number of requests that need to be
made. This is the aw_requests_remaining
counter. Then, as write address
burst requests get accepted by the
bus,
the counter decrements and a second counter, aw_bursts_outstanding
, gets
incremented. (The units of the two counters aren’t quite the same–the first
counter counts beats, the second bursts–but we can ignore that for now.)
Finally, when a burst is complete, the aw_bursts_outstanding
counter gets
decremented. Once both counters reach zero, the operation is complete.
The way the local reset, a.k.a. abort, works is that it empties out the first chamber of requests still to be issued, but then it lets the rest of the operations proceed to completion.
Let’s look at this in more detail.
The first counter keeps track of the number of remaining bus word transactions. When the core, isn’t busy, this number is set from the AXI-Lite interface registers,
It gets cleared on any local reset request, whether on a user abort or upon receiving a returned bus error.
Finally, on the first beat of any AWVALID
, possibly even before AWREADY
,
we adjust the number of words remaining to be written. That’s the meaning of
phantom_start
below–it’s true on the first clock cycle of
any new burst write request. Unlike AWVALID
, phantom_start
is only ever
high for the first cycle. It’s like what the AWVALID
signal would be if
AWREADY
were always true.
You might also wish to notice the other flag associated with our counter:
aw_none_remaining
. This flag is equivalent to aw_requests_remaining == 0
,
and it is used to simplify the logic
following so
I don’t have to check every bit of the counter in order to know if it’s zero.
We still need one other counter before we can know if we’ve completed our bus transactions, and that’s the counter that keeps track of the number of outstanding burst transactions. This particular core maintains both a counter and a flag for this purpose–just like we did above. The counter keeps track of the number of outstanding bursts, whereas the flag keeps track of whether only a single burst is outstanding or not.
Now that these two counters and their associated flags have been defined, we finally have enough information to determine if all of our outstanding bus transactions have completed or not.
This w_complete
flag is then used to send our state machine back to idle,
by clearing the r_busy
flag. Once cleared,
the core
has finished any user commanded reset, and can return to its idle state.
The key point here is that even though there are several steps required to reset an AXI master, those steps actually fit in nicely with the logic of building the AXI master in the first place.
A second key point is simply this: plan ahead for any local reset functionality you may wish to build into your own design.
Conclusion
Knowing how to reset a single user core in the context of a larger bus is an important part of building any bus component. The task isn’t nearly as simple as it might sound, simply because a local reset shouldn’t require a change in behavior from any of the other cores attached to the same system bus. Further, if you don’t do it right, you risk locking up the entire system–something that is generally considered quite bad.
One specific worry includes usage counters found within any bus interconnects. Such counters are required to keep track of the number of outstanding transactions, so that the interconnect can know when to adjust any channel assignments without risking returns getting sent to the wrong bus masters.
The good news is that by following the steps outlined above, you can rest assured that your core can still be reset separate from the rest of the design–even in the presence of any ongoing bus activity.
Therefore if any man be in Christ, he is a new creature: old things are passed away; behold, all things are become new. (2 Cor 5:17)