Badwolf wrote: 31 Jul 2024 17:25
What do you mean by 'not good for' in this case?
Not good, in this context, means that chances that you will not meet hold or setup requirements (probably setup would be ok, hold is more problematic).
Any flip flop requires the input data to be stable from a determined amount of time before the active clock edge (setup time) until a determined amount of time (hold time) after the edge. If these requirements are not met, the behavior is not predictable. The output might take the old value, or the new value, it is unknown. Furthermore, the output might become metastable (although this is very unlikely).
Setup time is usually easy to meet at slow frequencies, even with some clock skew. But hold time doesn't depend on the frequency, and can be violated even with very small skew if data input changes too close to the clock edge. This is why it is very common to work on the opposite edge of the clock on external interfaces. You sacrifice setup time (essentially as if you would double the frequency), but you gain a lot of slack on the hold time.
Meeting timing is especially problematic when you interface modern and old school technology. Old school slow technology typically has very large hold timing requirement. This is no problem as long as the transmitter is also slow, because Tco (time to clock output) would usually be even larger. But if the transmitter is too fast, chances that it would change the output too soon and it would violate hold timing at the receiver. This could happen even without any skew at all. This is another reason why it is common to work on a negated clock.
Now, if the output is not 100% predictable, it might not be a problem by itself. Depending on the case, you might not care. If it kept the old value, it will change at the next cycle as long as the data signal is stable. And in many cases this is good enough.
The big problem is when you read the external signals at multiple registers at the same time. Let's consider something like this:
Code: Select all
module noSyncr( input extInput);
reg r1, r2;
always @( posedge clk) begin
r1 <= extInput;
r2 <= extInput;
end
You might expect that at any given cycle, extInput would have the same value everywhere you read it, and then as a consequence, r1 and r2 would always be the same, but they might be not. As described above, if timing is not met, r1 might take the old value and r2 the new one. This is, of course, a trivial case just for illustration. In many cases it might be very difficult to see the problem at all.
If we add a synchronizer, then we avoid the problem:
Code: Select all
module withSyncr( input extInput);
reg r1, r2, extSynced;
always @( posedge clk) begin
extSynced <=extInput;
r1 <= extSynced;
r2 <= extSynced;
end
Now you are guaranteed that r1 and r2 would always be the same.
A single synchronizer doesn't still address the metastability problem. For that you need a syncrhonizer chain. I won't elaborate here as it would be too long and there are many online references. Metastability is very unlikely, especially on modern tech. In some cases you might not care. Say, if we assume that you might get a wrong blitted pixel once per month, who cares. But it is important when you are synchronizing a clock itself. You already seem to have a synchronizer chain on
CLK_D. Not sure if this was by design, or it was only for delay purposes. But as long you don't use the first two bits in the shift register, you should be safe. I would add a synchronizer chain for DTACK and AS as well. But again, might be not critic.
Doesn't seem to be the best and most intuitive elaboration that I've seen on the subject, but yes, that is the topic.
Mmm. I'm going to investigate what the onboard DRAM does in relation to the blitter as soon as I've fixed my test harness. Thanks for that.
I was trying to check your SDRAM controller timing, but access from Blitter seems to be disabled. Are you sure you pointed me to the right version?:
Code: Select all
wire altram_access_int = AS_INT | ( altram_access & rom_access );
...
nouveau_sdram sdram(
.CLK(RAMCLK),
// .RST(RST_IN),
.RST(RST),
.AS( altram_access_int ),
...
ACTIVE <= AS | ( UDS & LDS );
ACTIVE is asserted only when AS_INT (CPU AS) is asserted???
The latter. It's just a multi-stage divider. I use CLKOSC for the SDRAM, CLKOSC/4 for the 'fast' mode and the domains are allowed be switched on the CLKOSC_2. I think the last stage is unused and likely optimised out.
I hadn't thought about skew being an issue here, or this being an overly skew-inducing derivation technique.
Would a single always() block and non-dependent derivations be less of a problem?
That would be better because it would avoid the skew between CLKOSC_2 & CLKOSC_4, but you would still have skew in relation with CLKOSC. Ideally you should not divide the clock at all, just use clock enables. I realize that here you must output a clock externally, so you can't avoid some skew (not without a PLL).