![]() |
|
|
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.
There are three basic elements to clocking.
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.
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.
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.
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.
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
Copyright (c) 1998-2003 Brigham Young University. All rights reserved.
Last updated on 11 May 2006