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
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
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
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.
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
is available, the registers within the design can be reset using it.
Returning an error during the local reset
into something where any write during a
LOCAL_RESET would produce an
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
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
between your component and the rest of the
and then adjust the reset line to the core below the
you see fit. Any ongoing slave transaction hiccups will be caught by the AXI
and used to prolong the reset of the component core–returning AXI
(a type of AXI bus
until the core is fully re-integrated back into your system.
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
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
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
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.
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.
RREADYneed to be set to one if they aren’t already
I personally like to keep
RREADYhigh 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 || AxREADYis true on any of the address request channels, the
AxVALIDline should to be dropped. That way, no new transaction requests are issued.
This assumes that you don’t set
WVALIDhigh prior to its associated
AWVALIDsignal. 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 allow
WVALIDto be set prior to
AWVALID, then you’ll need to make sure to flush the
AWVALIDlines as well, just like we’re about to flush the
As soon as
!WVALID || WREADY, you will want to set
S_AXI_WSTRBto 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
1as 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
1as long as you have an outstanding transaction to complete. This works since you’re setting
WSTRBto 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
INRESETstate, 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
Once all transactions have completed, your core may then leave the reset state and return to
WORKING–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
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
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.
Once either of these signals are set, either
begins a local reset sequence that will return it to its idle state. This
includes aborting all current and ongoing transactions.
so no additional action is required there.
AWVALID needs to be dropped. In this particular
a combinatorial register is used to determine when to set
AWVALID in the
first place. This register is called
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.
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.
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
the bus to talk to
WSTRB signal similarly straightforward: anytime the
WSTRB is adjusted. If there’s no abort pending,
is set to all ones. (As currently written,
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
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
the counter decrements and a second counter,
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.
Finally, on the first beat of any
AWVALID, possibly even before
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
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
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.
w_complete flag is then used to send our state machine back to idle,
by clearing the
r_busy flag. Once cleared,
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.
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)