I find myself in a similar situation, Ironstein.  In my application I need to support Spartan-6 FPGAs (requiring use of XST) and also Artix-7 FPGAs (requiring Vivado in some cases, and XST or Vivado with other models).  I’ve become facile with the memory and clock management tiles.

I agree with your assessment that using IPcores is easiest, but then from a project management standpoint becomes tricky as there are so many files which need to be at the correct location 'just so'.

I am a bit wary of inferred designs in general as the slightest change may 'break' the DSP architecture, or some of the subtleties of how registers are allocated as Alex.Forencich pointed out.

I prefer to have explicit control of precisely what takes place, and so I am on the arduous learning curve to understanding the DSP48A1 block in Verlog from the bottom-up, beginning with simple implementation of an 18-bit adder/subtractor producing a 19-bit result.  This could of course easily be implemented in fabric using the '+' and '-' operators, but I am deliberately 'wasting' DSP48A1 blocks as a simple first step to using the DSP48A1 block. For computing dot products of integer vectors, or matrix multiplication, I’ll then move on to simple state machines which will be able to compute the results sequentially or across multiple DSP48A blocks.

If I get it figured out, I’ll post some Verilog source code to GitHub since there is no commercial intent in what I'm doing, and so I could poke it all out into the public domain.