Learning BSV‎ > ‎Advanced Bluespec‎ > ‎

Multi Clock Designs

Bluespec, by default uses positive clock edge clocked devices with reset asserted low... But what's a designer to do if they need to do something else? There are several answers, depending on what you want to do.

All neg edge clock and/or pos reset

The easiest case is if your whole BSV design uses neg clock edges or positive reset. The compiler and scheduler fundamentally don't know (or really care) which sense of clock or reset you are using (with one exception mentioned later). The Bluespec compiler uses a basic Verilog library (in $BLUESPECDIR/lib/Verilog) of bluespec elements which are imported into Bluespec. Basically, one can simply modify the existing Verilog files, changing all the always@(posedge clk) to always@(negedge clk), for instance, and when you simulate with Verilog and synthesize with Verilog, use this new library instead of the existing posedge library.

You could also create a Reset Inverter to change your positive reset to a negative reset and simply invert the reset at the top level of your design.. I.E.

  Reset reset   <- exposeCurrentClock();
Reset reset_n <- mkResetInverter( reset_by reset );

Presuming your synthesizer is smart enough to make an inverter to a FF reset properly (which surely it is), you just need top be sure you synthesize the inverter also at some point..

Your BSV code doesn't change because, as you know, we don't specify edges or reset senses in BSV, and you can't perform logical operations on either. Clock gating, dividers, etc, always goes to Verilog library elements.

But there are two hidden dangers as of this writing that need to be fixed via post processing (with your favorite scripting language)..

1. $display statements and other system calls are presumed to be positive edge triggered with neg reset. So if you look at the generated Verilog files, you will find code that looks like this:

   always (negedge CLK)
if (RST_N)
$display("your message here");

Which says that while the registers are clocking on the posedge, we are looking at data on the negdge edge (so this needs to change to posedge). If you want to change your reset to positive reset, then you must also change RST to !RST.

2. inlined registers - Bluespec generates always blocks also for registers. You can post process the Verilog always blocks to use negedge instead of posedge, or it may be easier just to explicitly use registers by compiling with the "-no-inline-reg", and rather than always blocks, each register is specified as a module like so:

 // submodule uut_state1_value_reg
RegN #( /*width*/ 5, /*init*/ 1) uut_state1_value_reg(.CLK(CLK), .RST_N(RST_N), .D_IN(uut_state1_value_reg$D_IN),
.EN(uut_state1_value_reg$EN), .Q_OUT(uut_state1_value_reg$Q_OUT));

I expect that both of these will be handle via an integrated solution soon :)


Feel free to contact bluespec if your project uses positive reset for the latest information..


Boundary Flops

You may only need a special kind of flop (like a neg edge flop in a pos edge design) at the very boundary of the design. Conceptually, if you simply need a flop (or latch or ???) on the way out of a BSV block to a pad, or Verilog design you can simply use import BVI to create an instance of a negative edge clocked FF. You then instantiate what you need and drive the input from BSV to the instance, and the output to the output of a value method. BSV doesn't provide anything in the way of checking or safety if you do this, and you will be responsible for making sure you don't then drive the output back in BSV... (well, unless you know what you are doing :)

Clock Regions

A more correct and complete means of using multiple clock edge devices is to create clock domains for each type of clock (i.e. posedge clocked data and neg edge clocked data) and to synchronize between each. Bluespec provides safe synchronizers, but with careful design practices and design implementation rules, you could create your own synchronizers with import BVI, which are just dual flips flops (or single flip-flops - see the DDR example).

Import BVI allows input and output pins to be associated with specific clocks brought into the module. You can write your Verilog module as you need, but be careful to specify the correct clock relations for each input and output pin.. This is what the compiler will use to enforce what pins are in what clock domain... (so you don't accidentally cross connect between clock domains causing unexpected problems).

DDR

An interesting multiclock case is the DDR design, where a designer wished to run logic on both pos edge and neg edge clocks..

To summarize, my initial approach to this is to create and import several new register types to create and deal with crossing between the two clock domains... I'll call them the Pos domain (output data is driven by pos clock edge device), and Neg domain (output data is driven by neg clock edge device)... This gives us 4 types of registers that we need to manage..

1. mkReg = the default Pos domain in, Pos domain out.. 2. mkRegPN = Pos domain in, Neg domain out 3. mkRegNP = Neg domain in, Pos domain out 4. mkRegNN = Neg domain in, Neg domain out

The point to this now is that we can know that data will be stable on the edge driven by the clock domain, and the inputs are stable to the rules as bsc expects. Double pumped logic is pipelined with data being alternately loaded between mkRegPN and mkRegNP. Of course, you will have to keep track of how much logic is there, since when you synthesize you now have 1/2 the cycle time..

A final trick here, is that when you want/need data driven double speed from both a posedge and neg edge, we now have another choice to make.. We can either send one of the signals through a null synchronizer and then we can just or the signals together.. Or one could create four more types of registers that now translate in and out of pos/neg clock domain to a new DDR domain, which is clocked registers that are loaded on pos *OR* neg edge clocks... I think you can now see advantages and disadvantages to each.. Perhaps you want to go directly from pos domain to DDR domain and back, ignoring completely the neg domain..

The point is to let the compiler help manage your domains, so you don't accidentally cross..

Here is an example (files can be downloaded from here [1])

 import MultiClock::*;
import StmtFSM::*;

 (* synthesize *)
module mkDDR();

// system clock
Clock clk <- exposeCurrentClock;

 // same clock speed, but different name so we have a different clock domain.
// registers clocked by ddrclk, clock on both pos *and* neg edge of the clock
Clock ddrclk <- mkNewClock;

 // this is a positive clock, as normal
Reg#(int) stage1 <- mkReg(0);

 // drive from pos stage1 to ddr clock domain
ReadOnly#(int) stage1cross <- mkNullCrossing( ddrclk, stage1 );

 // these run at double speed (ddr clocked)
Reg#(int) stage2 <- mkRegDDR(clocked_by ddrclk);
Reg#(int) stage3 <- mkRegDDR(clocked_by ddrclk);

 // cross back to pos domain, clearly gotta make sure we drive data from correct edge, etc
ReadOnly#(int) stage3cross <- mkNullCrossing( clk, stage1 );
Reg#(int) stage4 <- mkRegU;

 ////////////////////////////////////////////////////////////
// rule enables stage2 register to clock data crossed from stage1
rule step1;
if (stage1cross == 0)
stage2 <= 0; // just for debugging (reset data)
else
stage2 <= stage1cross + stage2;
endrule

 // it's not obvious, but this is running 2x
// since that's what stage3 is clocked by :)
rule step2;
if (stage2 == 0)
stage3 <= 0;
else
stage3 <= stage2 + stage3;
endrule

 // and back to 1x
rule step3;
// stage4 <= stage3; <= ERROR: can't do this - this cross clock domains
stage4 <= stage3cross;
endrule

////////////////////////////////////////////////////////////
// push stuff into stage1
Stmt test = seq
stage1 <= 10;
stage1 <= 12;
stage1 <= 14;
stage1 <= 16;
stage1 <= 20;
stage1 <= 100;
stage1 <= 0;
noAction;
noAction;
noAction;
noAction;
noAction;
noAction;
noAction;
noAction;
noAction;
noAction;
endseq;
mkAutoFSM (test);

 // now watch it come out!

rule showOut;
$display("stage4 = %h", stage4);
if (stage4 == 20) begin // what comes out if 0 is the input
$display("Done");
$finish;
end
endrule
endmodule

NOTE: again, display statements are not clocked properly on this altered clock domain, so if you use $display, be aware of when it is really clocking...

Bluesim

As of this writing, most of this will not work on bluesim. Bluesim schedules simulations via the TRS semantics, it is not an edge triggered or event driven simulator.. We have looked at various ways around this, but for the moment, tricky clock games will have to be simulated in a verilog simulator.


Comments