跨时钟域的设计
参考自fpga4fun.com
part 1.跨时钟域的信号
如果时钟域B需要使用来自时钟域A的信号,那么需要对这个信号进行同步。
如果输入信号比起时钟B来讲变化较慢,可以使用两个触发器来完成
1: module Signal_CrossDomain(
2: input clkA, // we actually don't need clkA in that example, but it is here for completeness as we'll need it in further examples
3: input SignalIn_clkA,
4: input clkB,
5: output SignalOut_clkB
6: );
7:
8: // We use a two-stages shift-register to synchronize SignalIn_clkA to the clkB clock domain
9: reg [1:0] SyncA_clkB;
10: always @(posedge clkB) SyncA_clkB[0] <= SignalIn_clkA; // notice that we use clkB
11: always @(posedge clkB) SyncA_clkB[1] <= SyncA_clkB[0]; // notice that we use clkB
12:
13: assign SignalOut_clkB = SyncA_clkB[1]; // new signal synchronized to (=ready to be used in) clkB domain
14: endmodule
part 2 跨时钟的Flag
当信号是一个短脉冲时
1: module Flag_CrossDomain(
2: input clkA,
3: input FlagIn_clkA,
4: input clkB,
5: output FlagOut_clkB
6: );
7:
8: // this changes level when the FlagIn_clkA is seen in clkA
9: reg FlagToggle_clkA;
10: always @(posedge clkA) FlagToggle_clkA <= FlagToggle_clkA ^ FlagIn_clkA;
11:
12: // which can then be sync-ed to clkB
13: reg [2:0] SyncA_clkB;
14: always @(posedge clkB) SyncA_clkB <= {SyncA_clkB[1:0], FlagToggle_clkA};
15:
16: // and recreate the flag in clkB
17: assign FlagOut_clkB = (SyncA_clkB[2] ^ SyncA_clkB[1]);
18: endmodule
1: module FlagAck_CrossDomain(
2: input clkA,
3: input FlagIn_clkA,
4: output Busy_clkA,
5: input clkB,
6: output FlagOut_clkB
7: );
8:
9: reg FlagToggle_clkA;
10: always @(posedge clkA) FlagToggle_clkA <= FlagToggle_clkA ^ (FlagIn_clkA & ~Busy_clkA);
11:
12: reg [2:0] SyncA_clkB;
13: always @(posedge clkB) SyncA_clkB <= {SyncA_clkB[1:0], FlagToggle_clkA};
14:
15: reg [1:0] SyncB_clkA;
16: always @(posedge clkA) SyncB_clkA <= {SyncB_clkA[0], SyncA_clkB[2]};
17:
18: assign FlagOut_clkB = (SyncA_clkB[2] ^ SyncA_clkB[1]);
19: assign Busy_clkA = FlagToggle_clkA ^ SyncB_clkA[1];
20: endmodule
part3 task
1: module TaskAck_CrossDomain(
2: input clkA,
3: input TaskStart_clkA,
4: output TaskBusy_clkA, TaskDone_clkA,
5:
6: input clkB,
7: output TaskStart_clkB, TaskBusy_clkB,
8: input TaskDone_clkB
9: );
10:
11: reg FlagToggle_clkA, FlagToggle_clkB, Busyhold_clkB;
12: reg [2:0] SyncA_clkB, SyncB_clkA;
13:
14: always @(posedge clkA) FlagToggle_clkA <= FlagToggle_clkA ^ (TaskStart_clkA & ~TaskBusy_clkA);
15:
16: always @(posedge clkB) SyncA_clkB <= {SyncA_clkB[1:0], FlagToggle_clkA};
17: assign TaskStart_clkB = (SyncA_clkB[2] ^ SyncA_clkB[1]);
18: assign TaskBusy_clkB = TaskStart_clkB | Busyhold_clkB;
19: always @(posedge clkB) Busyhold_clkB <= ~TaskDone_clkB & TaskBusy_clkB;
20: always @(posedge clkB) if(TaskBusy_clkB & TaskDone_clkB) FlagToggle_clkB <= FlagToggle_clkA;
21:
22: always @(posedge clkA) SyncB_clkA <= {SyncB_clkA[1:0], FlagToggle_clkB};
23: assign TaskBusy_clkA = FlagToggle_clkA ^ SyncB_clkA[2];
24: assign TaskDone_clkA = SyncB_clkA[2] ^ SyncB_clkA[1];
25: endmodule
To move a data bus (2 bits wide or more) from one clock domain to another, we have several techniques to our disposal.
Here are a few ideas.
- Gray code: If the data bus is a monotonic counter (i.e. only incrementing or decrementing), we can convert it to a gray code, which has the ability to cross clock domains (under certain timing conditions).
- Data freeze: If the data bus is non-monotonic, use a flag to signal the other domain to capture the value (while it is frozen in the source clock domain).
- Data burst: If the data bus has many consecutive values that need to cross the clock domain, use an FIFO, where you push values from the source clock domain, and read back values from the other domain.
That's all folks!
OPTIMISM, PASSION & HARDWORK