We tend to simplify how rules work by saying they have 3 basic attributes:
In reality, rules are smarter than this simple explanation because this allows us to fire as many rules as possible and functionally correct in a cycle. So in more detail we have.
So a bit more. Wires are relatively simple if you follow my idea of using registers first, then changing registers to wires. Otherwise, wires are a bit special.. They can break the idea of sequence of urgency evaluation and earliness execution. I.E. if a wire leaves on rule and goes into another rule, then we can't decide if that second rule can/will fire until we *execute* the first rule. So this affects how rules may or may not fire, as you can imagine... Also, conflicts are not detected on action methods as strictly as I described. Wires do only allow one write.. Most others may allow more than one call per cycle (depending on the internal schedule developed in the called module). Essentially the compiler verifies "atomicity" of rules in determining if they can fire in sequence or not.. If two rules can execute in any order and still get the same result, then they don't conflict with each other. If two rules get different results if you execute one before the other vs vice versa, then they conflict. The compiler obviously needs to simplify this at places because it can't see dynamic data values (umm, necessarily, and perhaps not yet?). If execution order matters and there is no obvious information about what the order should be (i.e. an obvious schedule or descending_urgency, etc), then the compiler randomly picks one to rule over the other.. In small examples this can be hard to follow. In practical examples, there tends to be other things going on in rules which will force a particular schedule so that the constants or register reads/writes fall from that... Also, that two rules write to the same register in a cycle rarely happens, but when it does, the effect is the same after two cycles as if one rule ran one cycle and other other ran the next cycle, from the point of view of both rules having executing.. This is a by product of TRS, again, so in cases where this unexpectedly happens (and there are cases where we do this on purpose for speed), one works around this by either ensuring the rules don't fire together or forcing a order as needed. So in practice, all this describes more exactly what I guess I have come to know as "the compiler does the right thing". But it helps to think a little about the software sequential angle of scheduling this stuff as a functional language might. The "swap conundrum" Aka, it's trivial in verilog, so why is it harder in bsv ? Well, the answer lies in the future :) Registers and cycle times are a somewhat artificial boundary applied to bsv code so that we can generate real hardware. But in the future, bsv may be creating rules that fire more than once in a cycle, untimed logic, multicycle logic, etc, etc. So we need to be careful that how we think about a register fits into the way rules and TRS is supported.. First consider that registers are like all modules in that inputs need to be valid at the beginning of the cycle (or really at the beginning of the step - meaning when this rule fires), and outputs are loaded at the next clock edge (though they are really valid at the end of the step).. The classic swap case fails in BSV because ruleA needs to read reg y at the start of step1, and write x at the end of step 1. That means ruleB then attempts to read X at the beginning of step 2, and y writes at the end of step 2, which wants to be driven into ruleA step1.. That's a step back in time, as it were, which is not allowed. This generates a scheduling error... rule ruleA; x <= y; endrule
Since register x must read before write, this schedule doesn't work. And the reverse case has the same problem with y. How to fix this? The best way is to put both writes in the same rule. clearly real hardware doesn't have a problem reading and writing registers, but since BSV wants to be able to schedule regardless of clocks (which is impossed more as BSV may start to fire rules more than once in cycle in future implementations), this adds a bit of quirk.. So this works: rule swap; x <=y; y <= x; endrule Schedule wise, step 1 reads x and y ta the beginning nd writes x,y at the end... On top of all this, at the end of the cycle, all rules are re-executed potentially, and of course registers get reloaded with outputs of other registers.. For that matter, all hardware is just a series of state driving to combinational logic then loading back into the same state potentially (pipelines are a simplified case of this) as we show in some of the training slides... Interesting Pragmas (Attributes) for rules.. Please check the reference manual for complete details...
(* fire_when_enabled, no_implicit_conditions *) |
Learning BSV > Advanced Bluespec >