B-Lang-org / bsc

Bluespec Compiler (BSC)
Other
954 stars 146 forks source link

Rules with conflicts can happen at the same cycle #732

Closed uv-xiao closed 2 months ago

uv-xiao commented 3 months ago

I encounter a situation where two rules that have conflicts (writing to the same register) can happen at the same cycle. The design and testbench are as follows:

interface DelayInterface;
  method Action feedSignal(Bit#(4) signal);
  method ActionValue#(Bit#(4)) get;
endinterface

(* synthesize *)
module mkDelayModule (DelayInterface);
  Reg#(Bit#(4)) delay1 <- mkReg(0);
  Reg#(Bit#(4)) delay2 <- mkReg(0);
  rule often;
    delay2._write(delay1<<1);
    $display("often: enq %d*2", delay1);
  endrule
  rule odd ((delay1 &1) == 1 );
    delay2._write(delay1);
    $display("odd: enq %d", delay1);
  endrule
  method Action feedSignal(Bit#(4) signal);
    delay1 <= signal;
  endmethod
  method ActionValue#(Bit#(4)) get;
    return delay2;
  endmethod
endmodule

module mkTestbench(Empty);
  DelayInterface delay <- mkDelayModule;
  Reg #(int) step <- mkReg(0);
  rule rl_step;
    step <= step + 1;
    if (step == 6) $finish;
  endrule
  rule rl_1 (step <= 4);
    Bit #(4) signal = truncate(pack(step+1));
    delay.feedSignal(signal);
    $display("%2d: feed %2d", step, signal);
  endrule
  rule rl_get (step >= 2);
    let signal = delay.get();
    $display("%2d: get  %2d", step, signal);
  endrule
endmodule

The bsim give the following output:

often: enq  0*2
 0: feed  1
often: enq  1*2
odd: enq  1
 1: feed  2
 2: get   1
often: enq  2*2
 2: feed  3
 3: get   4
often: enq  3*2
odd: enq  3
 3: feed  4
 4: get   3
often: enq  4*2
 4: feed  5
 5: get   8
often: enq  5*2
odd: enq  5
 6: get   5
often: enq  5*2
odd: enq  5

It shows that both the rule often and odd happen in steps 1, 3, and 5, where the odd rule shadows the often rule. I wonder why this would happen.

I appreciate any help you can provide.

mieszko commented 2 months ago

so this is how the Reg semantics are defined to be, isn't it?

from src/Libraries/Base1/Prelude.bs in lines 1846 et seq:

vMkReg v =
    module verilog "RegN" (("width",valueOf n), ("init",v)) "CLK" "RST" {
        read = "Q_OUT"{reg};
        write = "D_IN"{reg} "EN";
    } [ read <> read,
        read < write,
        write << write ]

basically what the last line says is that if you have two rules that write diff values to the same reg then bsc can pick one of them to logically happen "before" the other "in the same cycle," which means that the "logically later" write will trump the other one.

note this only happens when there are no ordering constraints in the other direction, though. if you had two rules where one copies reg x to reg y and the other copies y to x, they won't get scheduled in the same cycle because there is no way to pick the one that happens "logically before."

basically the question that the bsc scheduler is asking is, is there a way to schedule some rules in one cycle so that the overall outcome can be explained by some sequence of these rules being applied individually one after the other? if so, it will schedule them together. this is how the rule semantics work.

a trivial way for this to happen is of course if the rules don't access the same state. a less trivial (but still trivial) way is if both rules read the same state; you can see this in <> above.

a still less trivial case is that one rule reads a reg and the other writes the same reg; you can see this with the < case above, which requires the read to occur "logically before" the write (because otherwise a RAW hazard would ensue). notice that actually this is what makes a register different from a wire from a scheduling perspective: for a wire the write would need to come before the read (which you can verify in src/Libraries/Base1/PreludeBSV.bsv:121).

the final way for this to happen is what you saw: if both rules write then if you let them both happen but one clobbers the other's write, then it still appears as if they happened in some sequence provided you can't observe the intermediate state in between the two rules happening in this sequence. at least that is the intuition behind this behaviour.

i'm personally not a fan of this being a thing with Reg either — i too would prefer for reg writes to conflict and the writer having to explicitly choose one — but it has always been thus from the beginning of time and i don't see it changing for Reg. (of course you could make your own version of Reg that behaves differently by making a different vMkReg and the relevant wrappers, although it that case it might be wise to change the type name as well to avoid confusion.)

uv-xiao commented 2 months ago

Thanks a lot for the clear explanation!