50.002 Computation Structures
Information Systems Technology and Design
Singapore University of Technology and Design
Clocks
This document is written to guide you with generating new clock signals for your project should it fails to meet timing specifications. If your project works in simulation but behaves erratically on physical FPGA, it is likely that you have timing issues.
Motivation
The Alchitry Au FPGA board runs at a default system clock of 100 MHz, which is generated by an onboard oscillator. It is a crystal oscillator module soldered onto the board that provides a clean 100 MHz square wave clock signal. However, sometimes your design might fail to meet the timing constraints, e.g: ALU multiply takes too long and violates the t2 constraint:
To make your project works reliably, you need to add extra logic to slow your FSM (let it loop for several cycles before capturing its result) or to slow down the clock. The latter is more straightforward but cumbersome to do: it requires you to access Vivado and generate clock with another frequency.
Changing the clock frequency in the alchitry.acf
constraint file does not change the default clk
signal going to your alchitry_top
. The constraint file is for telling the tools what the frequency is for timing analysis only, not to control the frequency.
Method 1: Delay the FSM
If you need more than 10 ns (since the onboard clock is fixed at 100Mhz) of time per state, for instance: your ALU’s tpd for CMPEQ
or MUL
is taking too long, you need to loop a few times in the same state before capturing the output of your ALU. Otherwise, your ALU won’t be abl to output the correct value in the same clock cycle.
The following state won’t behave as expected. We won’t store the right CMPEQ
result into R6
in this state for example:
States.CHECK_SCORE:
alufn = b110011 // CMPEQ
regfile_ra1 = d1 // score dff
asel = b00 // rd1 to alu
bsel = b01 // constant 10
regfile_we = 1
regfile_wa = d6 // temp dff
game_fsm.d = States.BRANCH_SCORE
You should have fixed your ALU to be able to output a stable result within 1 period of FSM clock (remove any sequential component). However, if you need to have any sequential (like division) or complex component (very long tpd) in the ALU, then you will need to wait several cycles in this state before advancing to the next state, e.g BRANCH_SCORE
.
You can do this by declaring a temp dff in your control unit / fsm module:
dff state_delay[4](.clk(clk), .rst(rst), #INIT(0))
And then utilise this in the state to introduce delay:
States.CHECK_SCORE:
// increment state_delay
state_delay.d = state_delay.q + 1
alufn = b110011 // CMPEQ
regfile_ra1 = d1 // score dff
asel = b00 // rd1 to alu
bsel = b01 // constant 10
regfile_wa = d6 // temp dff
if (&state_delay.q){
regfile_we = 1 // write only once at the end of the delay cycle
state_delay.d = 0 // reset state_delay
game_fsm.d = States.BRANCH_SCORE // go to next state
}
This way, the FSM maintains the same control signal for 16 onboard clock cycles (160ns) and then only capture the output at the the 16th cycle, while transitioning to the next state. This should give enough time for the ALU output to settle. You should adjust the size of the dff
accordingly.
You can find out how long you need to wait for the output of the ALU to stabilize by changing the clock frequency in alchitry.acf
file.
Method 2: Generate New Clock Signal using Clock Wiz in Vivado IP Catalog
In this method, we will use Vivado to use a MMCM (Mixed-Mode Clock Manager) to generate a custom clock signal from the default 100 MHz input clock.
MMCM
An MMCM (Mixed-Mode Clock Manager) is a clock management block in Xilinx FPGAs that generates derived clocks with configurable frequency, phase, and duty cycle.
Open Vivado IP Catalog from the component menu:
Wait awhile, and you will see Vivado launched:
Search FPGA Features and Design
, choose Clocking, then double click Clocking Wizard to launch:
The Clocking Wizard window will appear. At the Clocking Options tab, choose MMCM. The MMCM module will be expecting a 100MHz input signal, and is coming from the Single-ended clock capable pin, N14, from your Alchitry Au.
Then, at the Output Clocks tab, change the Requested Frequency to be the one you want, e.g: 10MHz. You might also want to change the component name to follow the frequency so you remember which component is producing what. For instance, clk_wiz_10
for 10MHz clock.
At the bottom, delete the optional reset
and locked
:
Finally, click OK then Generate:
It will take some time.
Once done, you can close Vivado. When you return to Alchitry Labs, you will see your new clock created:
You can then use it like any other module:
clk_wiz_10 slow_clock(.clk_in1(clk), .reset(rst))
.clk(slow_clock.clk_out1){
dff slow_counter[32](.rst(rst))
}
Test
This alchitry_top.luc
file blinks two LEDs:
module alchitry_top (
input clk, // 100MHz clock
input rst_n, // reset button (active low)
output led[8], // 8 user controllable LEDs
input usb_rx, // USB->Serial input
output usb_tx, // USB->Serial output
output io_led[3][8], // LEDs on IO Shield
output io_segment[8], // 7-segment LEDs on IO Shield
output io_select[4], // Digit select on IO Shield
input io_button[5], // 5 buttons on IO Shield
input io_dip[3][8] // DIP switches on IO Shield
) {
sig rst // reset signal
clk_wiz_10 slow_clock(.clk_in1(clk))
// dff using slow 10MHz clock
dff slow_counter[32](.rst(rst),.clk(slow_clock.clk_out1))
dff led_state_slow(.rst(rst), .clk(slow_clock.clk_out1), #INIT(0))
// regular 100MHz clock
.clk(clk) {
// The reset conditioner is used to synchronize the reset signal to the FPGA
// clock. This ensures the entire FPGA comes out of reset at the same time.
reset_conditioner reset_cond
dff counter[32](.rst(rst), #INIT(0))
dff led_state(.rst(rst), #INIT(0))
}
always {
reset_cond.in = ~rst_n // input raw inverted reset signal
rst = reset_cond.out // conditioned reset
led = 8h00 // turn LEDs off
usb_tx = usb_rx // loop serial port
io_led = 3x{{8h00}}
io_segment = 8hff
io_select = 4hf
led_state.d = led_state.q
led_state_slow.d = led_state_slow.q
counter.d = counter.q + 1
slow_counter.d = slow_counter.q + 1
if (counter.q == d10_000_000){
led_state.d = ~led_state.q
counter.d = 0
}
if (slow_counter.q == d10_000_000){
led_state_slow.d = ~led_state_slow.q
slow_counter.d = 0
}
io_led[0][0] = led_state.q
io_led[1][0] = led_state_slow.q
io_led[2] = counter.q
led = slow_counter.q
}
}
The two LEDs are:
io_led[0][0] = led_state.q // will blink 10x faster than the other LED
io_led[1][0] = led_state_slow.q
Summary
If your project works in simulation but behaves erratically on physical FPGA, it is likely that you have timing issues.
Whenever your project fails to meet timing constraints, remember to:
- Modify
alchitry.acf
to let Vivado do the timing analysis properly with the new clock frequency and ensure that it meets the timing constraints - Create a new clock using Vivado Clock Wiz (better choice), or manually delay certain FSM states where you suspect takes more than 10ns to complete (works, but more of a guessing game)