...when you're trying to deal with oddball stuff.
Like clock domain crossings.
The problem at hand isn't exactly a domain crossing; the two clocks are derived from a common source... but apparently there's enough timing uncertainty between them to give the timing analyzer heartburn.
I generally try to stick to behavioral, rather than structural, code as far as possible, because it ought to be architecture-independent. (Of course, I have to use the structural form for module instantiations, whether they're my own modules or vendor macros, but that's another matter.)
Looks like I can either construct a pair of synchronizers, or stick a totally vendor-specific macro into the high-speed domain.
Guess I'll sketch out how to build the kind of synchronizer I need in discrete logic, and then translate it into vendor primitives and combinatorial functions.
Update: After some review of Verilog techniques and a bit of poking around, I implemented what I think is the right thing, in behavioral code, nicely integrated with my other behavioral code. It compiled fine.
Then I wrote another one that does exactly the same thing only running the other way across the domain boundary. I get goofy compilation errors that seem to imply that the compiler is confused about what the code is supposed to do.
This is epically annoying. If I have to resort to structural forms, integrating with my behavioral code for the part that gets the work done will be awkward.
Update 2: Looks like the code that seemed to be compiling OK was actually being optimized out, because I hadn't connected the output yet. The compiler is in fact being consistent about failing to infer "D flip-flop with enable and asynchronous clear" from my logic, favoring an interpretation of "D flip-flop with enable and latch with clear, both trying to drive the same signal."
So, guess the next step is to lose the asynchronous resets and make everything synchronous, with connections back and forth across the clock domains. And, ugh: in one case (going from slower to faster), this implies some logic that may re-introduce the original timing problem, unless I add yet another flip-flop.
Update 3: Got it working, in synchronous form. Had to add that flip-flop to get rid of a timing error.
Then, as I tuned various conditions (trying to work around that "see a half microsecond into the future" thing), a timing error cropped up in some unrelated logic. Time to dial up the optimizations... and now compilation involves lots of thumb-twiddling time. Maybe I turned op a feature or two that would have been better left turned off. Or maybe I should have tried a newer version of the toolchain (but this is the one associated with the nailed-down release process for the ancestral product).