开云体育

ctrl + shift + ? for shortcuts
© 2025 开云体育

Using mkRWireUnsafe for communicating between methods


 

I'm making a free list in hardware - basically a LIFO that can be used to assign unique tags to bus transactions which are valid for the lifetime of the transaction. My free list module(mkTagEngine) has two methods, requestTag and retireTag. To call both methods simultaneously, requestTag needs to see what tag argument was passed to retireTag(at which point requestTag effectively "falls through" and returns the retired tag"). The only way I've found to communicate arguments between methods is `mkRWireUnsafe`. Why is this? What makes it unsafe? Should I be concerned about using unsafe?
?
Under the hood, does FIFOF use unsafe wires as well?
?
Here is the reference line of code:


 

Looking at Base1/PreludeBSV.bsv and comparing vMkRWire to vMkUnsafeRWire, it looks like the set and get methods can be used in the same rule in the unsafe version but not the regular version (the SB vs SBR scheduling annotations). As for FIFOF, I'm pretty sure it's a Verilog primitive.

But back to your problem: what to do in this case where you have two methods that interact somehow and you don't want to decouple everything through FIFOs or whatever?

One thing you can do is remove most of the side effects from the methods, and replace them with writing one mkDWire per method to transmit relevant information from the methods to the “core” of the module (mkDWire Nothing and fromMaybe are your friends). There, add an always-enabled rule that reads those wires and composes the requests however you wish. Finally, extract the response through a mkBypassWire (or you can be clever with mkDWire).

This does have some implications on your interface in the sense that you might need to separate your request and response methods (because they'd be separated by the rule in the schedule order). And of course you have to be careful to not use your module in ways that result in combinational loops, or decouple things by giving up on the notion of reusing a tag the moment it's freed (which you might or might not want to anyway depending on how you permit things to be combinationally linked).


 

I don't think I'm getting around using `mkUnsafeRWire`...
?
I tried refactoring where I have the "compute tag" rule compute the tag result and place it in a bypass wire. This means the requestTag method no longer has to be predicated on reading `methodRetireTagCalledValid`. Nevertheless, the compiler refuses to allow me to schedule `requestTag` and `retireTag` together in the same cycle with this approach. I think this is because fundamentally, I'm trying to simultaneously call two methods in the same cycle where there is information flowing between these two methods.
?
So my question now is:
Is it possible in Bluespec to call methodA and methodB simultaneously if the behavior of methodA depends on the argument passed to methodB without using `unsafe`?
?
In a highly concurrent asynchronous bus, it is very likely that the request interface of the bus often requests tags for transactions it wishes to start whilst the response interface simultaneously retires tags for transactions that have just completed. So not being able to simultaneously request and retire a tag will incur a huge performance penalty.
?
In Rust, unsafe means the borrow checker can't prove memory safety. What does unsafe mean in bluespec? What sort of guarantees am I foregoing by using `unsafe`?


 
Edited

But you didn't decouple the request and response (in requestTag) as I was suggesting you might have to.

Anyway, in this case at least, unsafe means that you can violate rule atomicity, and I would bet that this is usually what unsafe means.

Atomicity means that either the entire rule fires or none of it does. This by extension implies a sequential consistency property: that any observable state must be explained by some sequence of one-by-one rule applications (you can't observe state inside rules b/c they're atomic.) I think this is the equivalent of Rust's memory safety, Haskell's unordered side effects, and so on.

This requirement restricts the system's abilitity to compose rules (you can think of scheduling multiple rules in a cycle as rule composition). Consider

    when True ==> y := x + 1
    when True ==> x := y + 1

you might think that this is just like

    when True ==> do
        y := x + 1
        x := y + 1

but it isn't: there is no sequence of applying the two rules in the first example above that results in the state generated by the second example.

This is what you're breaking by asking the request method to fire the retire method in the middle of itself (and the rule in your updated version that doesn't refactor the request and response).

Anyway, I was not suggesting that you give up on requesting and retiring tags simultaneously, just that one option is to give up on the edge case where you reuse the tag immediately after you retire it (which may be reasonable for other reasons anyway). Other than the refactoring I suggested, you can decouple things by adding state: for example, you could have a "fresh tag" FIFO that you always take stuff from that effectively decouples the fresh-tag request from the state that needs to be updated. Or you can combine request and retire into one giant method that does everything, and deal with the pain of it being one method at the use point. It just depends what your constraints are (e.g., are you making one of the things or a million?) and what you feel is reasonable (e.g., where you're okay having combinational paths in the final hardware).


 

This is what you're breaking by asking the request method to fire the retire method in the middle of itself (and the rule in your updated version that doesn't refactor the request and response).

I think I see what you're getting at here. `requestTag` followed by `retireTag` leads to a different end state than `retireTag` followed by `requestTag`...
?
Is this the crux of the issue??
?
An acceptable alternative is we don't have to return the freshly retired tag for the edge case... We could instead return an older free tag... The downside of this is the pathological case where there are no free tags - then we can't retire and request simultaneously. This would imply a system that requests tags more quickly than it retires them... So this would be a load balancing issue rather than an issue with the TagEngine.


 

After giving this more thought, it seems that methods in a cycle must be atomic WRT other methods. Rules must also be atomic WRT other methods. BUT - if there are rules and methods to fire in the same cycle, methods must necessarily by be scheduled BEFORE the rules?
?
When I say atomic for methods, I mean that two methods can conceptually occur in any order WRT each other within a given cycle.
?
Likewise, when I say atomic for rules, I mean that two rules can conceptually occur in any order WRT each other within a given cycle.


 

I think I see what you're getting at here. requestTag followed by retireTag leads to a different end state than retireTag followed by requestTag...

That is true, but that is also true about my example with two registers above, and the one-rule version works fine.

But why?

There are two ways to compose the one := other + 1 actions sequentially (i.e., in different rules) — which are different — and there is a third way to compose the two actions calls in parallel which gives a different result from both of the sequential ones.

Why? Well, if you look at the scheduling annotation for vMkReg in Prelude.bs you'll see that read < write for a Reg. In a single rule this is fine: both reads happen first and then both writes. You can describe it like this: read < read < write < write.

But if you put them in two different rules, the scheduler can't do that anymore. To keep the rules atomic, it needs to order them read < write < read < write (where the first two are from one rule and the second two from the other). If you wanted to have the same behaviour as the one-rule version, you would have to interleave method calls from both rules, which would break atomicity, right?

An acceptable alternative is we don't have to return the freshly retired tag for the edge case... We could instead return an older free tag...

Yeah, this is one of the options that I was suggesting.

The downside of this is the pathological case where there are no free tags - then we can't retire and request simultaneously. This would imply a system that requests tags more quickly than it retires them... So this would be a load balancing issue rather than an issue with the TagEngine.

Not necessarily, it just implies a system that needs all of the tags to be in flight at some point — which, you could argue, if you don't need then why do you have so many tags — and can also request/retire concurrently when all of the tags are in flight. (But, on the other hand, you could also argue that one more tag than the number of requests you can service never hurt anybody, since you probably need one extra thing or a valid bit to indicate empty somewhere anyway, etc etc.)

It does, however, also imply a combinational path between the retiring tag and the request, which you might or might not want depending on how many “clients” this tag engine has, what your timing constraints are, what your PD partitions are, and so on. At some scale, I think you actually want the behaviour that doesn't require this combinational path regardless.


 

After giving this more thought, it seems that methods in a cycle must be atomic WRT other methods. Rules must also be atomic WRT other methods.

I think “in a cycle” is leading you astray here, because there are two ways to find yourself in the same cycle: being in the same rule, or being in two different rules that are scheduled in the same cycle. The behaviour is different (vide supra): the first is atomic by fiat, and the second needs to have an “explanation” that says which rule was fired first (atomically) and which second.

BUT - if there are rules and methods to fire in the same cycle, methods must necessarily by be scheduled BEFORE the rules?

Assuming you mean the “co-scheduled rules” interpretation of “in the same cycle,” I don't think that is true. A tiny counterexample that is an extremely simplified version of your tag engine:

{-# synthesize mkFoo #-}
mkFoo :: Module (Reg (UInt 8))
mkFoo = module
    r <- mkReg 0
    win <- mkDWire Nothing
    wout <- mkBypassWire
    rules
        "foo": when True ==> do
            wout := fromMaybe r win
            r := r + (if isJust win then 0 else 1)
    interface
        _read = wout
        _write = win._write ° Just

If you compile this with -dschedule, you will see that the rule is indeed scheduled to fire between the two methods.

And you can use them in another module:

{-# synthesize mkBar #-}
mkBar :: Module Empty
mkBar = module
    f <- mkFoo
    r <- mkReg 0
    rules
        "bar": when True ==> r := f
        "baz": when True ==> f := r

When you do that, foo is scheduled between bar and baz — which call the _read and _write methods on f — and you can see that if you remove the synthesize from mkFoo and compile with -dschedule. (Exercise for the Reader to see what happens if you try to do both in one rule.)


 
Edited

I've completely refactored my TagEngine into what I hope is a much more elegant and correct design. Some things of concern that I would like to confirm:
?
1. When rules and methods conflict, do methods take priority over a rule in a given cycle?
2. With what granularity can bluespec resolve conflicts? For example, in my `requestTag` method, we have the , which unlike other case arms in that method, does not actually write to `inUseVec`. This means that presumably, the compiler should be able to schedule the "retireTags" rule when `resetTagCounter` register is `Nothing` even though the "retireTags" rule also writes to `inUseVec`.
?
In practice, I am having trouble determining if this is the case. For one, deciphering `resetTagCounter` in VCD is challenging since bluesim doesn't take advantage of ASCII string support in VCD. Furthermore, the dumped schedule for "retire_tags" doesn't seem too mention the `requestTag` method:
?
```
Rule: tagEngine_retire_tags
Predicate: tagEngine_retireTag.whas && tagEngine_freeQueue.i_notFull
Blocking rules: (none)
```
?