|   JHDL Home Page   |   Configurable Computing Lab   |   User's Manual (TOC)   | Search

User-Defined Clocking and Multi-Clocking

Overview

Index

Preface

With the 0.3 public release, JHDL introduces new features to clocking. Previous versions of JHDL used a global clock that clocked all synchronous elements once per cycle. This simulated only the rising edge of registers, but did not allow any other flexibility. This is the default clocking described previously in this user's manual.

JHDL clocking has been rewritten to allow the use of multiple clocks, to simulate negative and positive edge triggered flip-flops, and to allow clocks to be gated.

Design of Clocking

There are three basic elements to clocking.

Synchronous Elements

Synchronous elements are those who require a clock to produce their outputs. This would include registers, memories, and any simulation models that use clock() methods. Synchronous elements can be rising-edge triggered, falling-edge triggered or both. When a rising or fallling edge is seen on the clock input of a synchronous element they will be "clocked". That is, the clock() method of the element will be called.

Clock Drivers

A clock driver is a simulation abstraction which was introduced to help simulate these new features. A clock driver is simply a pattern generator which generates the clock signal applied to synchronous elements. The clock driver generates a signal based on its schedule. The schedule is used to determine when synchronous elements should be clocked. Since clock drivers are a simulation abstraction and are not netlist-able, they should exist only in test benches, like stimulators.

Clock Wires

A clock wire is simply a wire which is connected to a clock driver and to the clock input of synchronous elements. These typically connect the clock driver directly to synchronous elements, but there may be other gates in between. If there are, it is called a gated clock circuit. Gated clocks slow down the simulation tremendously because the simulator doesn't know a priori when they are going to cause the clocking of synchronous elements. Thus, it cannot pre-schedule the evaluation of gated clock synchronous elements. You should not use gated clocks unless they are absolutely necessary in your design. If you do, the simulator will warn you in case you did it inadvertantly.

Default Clocking and User-Defined Clocking

When you use default clocking as described previously in this user's manual, the JHDL system automatically creates the three elements above for you. That is, when it first encounters a synchronous element in your design, it creates a clock wire for your design. It also creates a clock driver for your design and adds it to the top level simulation model. The schedule for this default clock driver is "01".

If you choose to specify your own clock(s), you should ensure that you use those clocks everywhere in your design. DO NOT MIX DEFAULT CLOCKING AND USER-DEFINED CLOCKING IN THE SAME DESIGN.

If your design contains only a single clock, there is likely no reason to try to define your own clock. User-defined clocks are usually only appropriate if your design must have multiple clocks. That said, the first example below will contain only a single user-defined clock to illustrate how to set a clock up.

An Example

This example is a test bench for a counter. It defines a new clock driver with a schedule of "10".


import byucc.jhdl.base.*;
import byucc.jhdl.Logic.*;
import byucc.jhdl.Xilinx.Virtex.*;
public class tb_cnt extends Logic implements TestBench {
 
  static VirtexTechMapper tm;
  static Cell c;

  static HWSystem hw;
  public static void main(String argv[]) {
    hw = new HWSystem();
    tb_cnt tb = new tb_cnt(hw);
    tm.setInsertPads(false);
    tm.setInsertTopLevelPorts(true);
    tm.netlist(c, "c.edn");
    new cvt( tb );
  }

  private Wire clr, q;
  
  public tb_cnt(Node parent) {
    super(parent);

    tm = new VirtexTechMapper(true);
    setDefaultTechMapper(tm);

    clr = wire(1, "clr");
    q = wire(4, "q");


    Wire clk = wire(1,"clk");  // Declare clock wire

    clockDriver(clk,"10");     // Instance new clock driver
    setDefaultClock(clk);      // Make all subsequent circuits use it


    c = new cnt(this, clr, q);

  }
}

There are three lines of interest in this example and they are commented. The first line simply creates a new one-bit wire to serve as the clock wire. The second line instances a new clock driver to serve as the source for the clock. In it, the schedule of "10" is specified. Finally, that clock is specified to be the default clock for all subsequent circuitry that is built. After that, the circuit is built. Each time during the build a synchronous element is encountered, its clock input is wired to to this default clock.

There are a few limitations on what can serve as a clock wire - any one-bit wire can serve as a clock wire. The clock schedule specified is a string that is at least length 2, consisting of 0's and 1's.

Multiple Clocks

From what was explained above, the astute reader may easily deduce what one may do to have a multiple clock design. Taking the same example, adding another counter with a new clock is fairly straight forward.


import byucc.jhdl.base.*;
import byucc.jhdl.Logic.*;
import byucc.jhdl.Xilinx.Virtex.*;
public class tb_cnt extends Logic implements TestBench {
 
  static VirtexTechMapper tm;
  static Cell c1, c2;

  static HWSystem hw;
  public static void main(String argv[]) {
    hw = new HWSystem();
    tb_cnt tb = new tb_cnt(hw);
    tm.setInsertPads(false);
    tm.setInsertTopLevelPorts(true);
    tm.netlist(c, "c.edn");
    new cvt( tb );
  }

  private Wire clr1, q1, clr2, q2;
  
  public tb_cnt(Node parent) {
    super(parent);

    tm = new VirtexTechMapper(true);
    setDefaultTechMapper(tm);

    clr1 = wire(1, "clr1");
    q1 = wire(4, "q1");
    clr2 = wire(1, "clr2");
    q2 = wire(4, "q2");


    Wire clk1 = wire(1,"clk1");  // Declare clock wire
    clockDriver(clk1,"0100");    // Instance new clock driver
    setDefaultClock(clk1);       // Make all subsequent circuits use it
    Wire clk2 = wire(1,"clk2");  // Declare another clock wire
    clockDriver(clk2,"0010");    // Instance new clock driver for it


    c1 = new cnt(this, clr1, q1);


    setDefaultClock(clk2);      // Make second counter use second clock
    c2 = new cnt(this, clr2, q2);

  }
}

Each of the two counters instanced use a different clock. It can easily be seen in the waves view of cvt that there are two different clocks and that the outputs of the counters are changing at different times.

The above example also points out an important idea: the setting of a default clock can be done anywhere. That is, a third counter clocked by clk1 could be instanced after the second counter by simply doing this:


    setDefaultClock(clk1);
    c3 = new cnt(this, clr3, q3);

When you define multiple clocks in a circuit, their schedules must be the same length. Thus, this is illegal:


    clockDriver(clk1,"01");
    clockDriver(clk2,"0011");

To get one clock to toggle twice as fast as another, you would do this:


    clockDriver(clk1,"0101");
    clockDriver(clk2,"0011");

Finally, note that in these two examples above, there is still some implicit clock handling. That is, clock wires are created and declared to be the default clock but those same clock wires are not passed into the circuit hierarchy in any way. The setDefaultClock() call takes care of ensuring that all synchronous elements get appropriately wired to whatever was the default clock as of the time they were created.

Explicit Clock Handling

There may be times when it is necessary to have finer control over clocking. For example, a single cell you design may require two clocks. In this case, the clock wires created in the testbench will have to be explicitly passed into that cell through ports like any other wire. Then, inside the cell, calls to setDefaultClock() can be used to specify which parts of the cell are clocked by which clock wire. Here is a part of a cell's constructor that shows this (all wires and ports other than the two clocks have been omitted for clarity):


import byucc.jhdl.base.*;
import byucc.jhdl.Logic.*;
public class twoclocks extends Logic {

  public static CellInterface[] cell_interface = {
    in("clk1", 1),
    in("clk2", 1),
    ...
  };

  public twoclocks(Node parent,
                   Wire clk1, Wire clk2,
                   ...) {
    super(parent);

    connect("clk1", clk1);
    connect("clk2", clk2);
    ...

    setDefaultClock(clk1);
    ... // Instance some synchronous circuitry here

    setDefaultClock(clk2);
    ... // Instance some more synchronous circuitry here

    setDefaultClock(clk1);
    ... // Instance yet some more synchronous circuitry here
  }
}

However, this may still not be enough control. There may be cases where the individual bits of a register need different clocks. All calls in Logic which generate synchronous circuitry have versions where the clock may be specified explicitly. Similarly, all the library primitives which are synchronous have versions of their constructors with the clock wire explicitly specified. These versions of the calls can be used to control which clock goes to which circuit element. This is called fully explicit clocking.

It is perfectly acceptable to mix the use of setDefaultClock() with fully explicit clocking. However, it is not recommended in that it may result in hard to track down bugs since the use of setDefaultClock() hides what is going on at times.

Here is simple design which uses fully explicit clocking:


import byucc.jhdl.base.*;
import byucc.jhdl.Logic.*;
public class twoclocksExplicit extends Logic {

  public static CellInterface[] cell_interface = {
    in("clk1", 1),
    in("clk2", 1),
    in("a", 8),
    in("b", 8),
    out("q", 8),
    out("s", 8)
  };

  public twoclocksExplicit(Node parent,
                   Wire clk1, Wire clk2,
                   Wire a, Wire b, Wire q, Wire s) {
    super(parent);

    connect("clk1", clk1);
    connect("clk2", clk2);
    connect("a", a);
    connect("b", b);
    connect("q", q);
    connect("s", s);

    reg_o(clk1, a, q);  // This register uses clk1
    reg_o(clk1, b.range(3,0), s.range(3,0));  // Half of this register uses clk1
    reg_o(clk2, b.range(7,4), s.range(7.4));  // The other half uses clk2
  }
}

It is important to note that there is no setDefaultClock() call above. It is not necessary since all synchronous element instanced have an explicitly specified clock. Also note that in this example, the test bench which instances this circuit is responsible to create the clock wires and drivers.

Gating the Clock

Gating the clock is allowed in JHDL. This means that the clock is combined with other signals using combinational logic, usually to control when the clock runs or stops. This design style is probably not the best strategy with FPGAs due to the large clock skew it will introduce, but it is allowed and it does simulate.

When using gated clocking, just treat the clock as any other wire. A typical use might be to AND the clock wire with a clock enable signal and then use the output of the AND as the explicit clock to a register.

Gated clock designs do not allow the simulator to schedule clocked events as it would in a non-gated circuit. Gated clock designs require the simulator to watch to see if a suitable transition has occured and evaluate the affected circuitry appropriately. This can be very costly as the number of differently gated flops is increased. Keep this in mind when designing gated clock circuits. Most FPGA's contain clock enable inputs on their synchronous elements. It will always be preferable to use those, if available, rather than gate the clock.

|   JHDL Home Page   |   Configurable Computing Lab   |   Prev (Simulator Callback)   |   Next (FSM Generators)   |


JHDL 0.3.45