A quick recap of how Perlin noise works:
It starts by finding the cell your sample point lands in within an integer lattice. From that it determines the integer points that define the corners of that cell. For each corner, it generates a pseudo-random gradient vector, and then computes the projection of your sample point's offset from the corner along this gradient vector. Interpolating those projections according to your sample position gives the final smooth noise value.
So, let's consider how this works when your sample lands directly on one of these corner points. The interpolation weight for all the other corners will be zero, and the projection onto this corner's gradient vector will also be zero, so the result is: zero.
At every set of integer coordinates, Perlin noise will give you a value of zero.
So, easy fix: don't sample at integer coordinates. Sampling at (x + 0.5, y + 0.5) for integers x & y will move your sample point from the corner to the center of the cell. You won't see much benefit of using a continuous noise function like Perlin noise that way though. Adjacent samples will have little correlation. If you want just a random number per tile, you could use a hashing function like Perlin noise uses internally to select a random gradient for each corner - skipping the overhead of doing it four times, computing dot products and interpolating the results.
Or, if you want continuity across multiple tiles, you should scale your sampling locations smaller than the feature size of the noise function. For instance, if you want large-scale features on the order of about 30 tiles wide, divide x & y by 30 to get a fractional sampling location that will gradually ramp up & down the hills and valleys within each grid cell of the noise function.