A lot of these answers use last or append, which are expensive operations.
You can do half of a reverse and then check equality.
The question stipulated not to use reverse, but this only uses a reverse like process, not a full reverse.
palindrome(Xs):- palindrome(Xs,[]).
palindrome(Xs, Xs).                    % [1,2,2,1] will get to pal([1,2],[1,2])
palindrome([X|Xs],Xs).                 % captures a case like [a,b,c,b,a]
palindrome([X|Xs],Ys):- palindrome(Xs, [X|Ys]).      % reverse-like process
One thing possibly missing from the above is cuts. Although no necessary here, should be used for good practice:
palindrome(Xs):- palindrome(Xs,[]).
palindrome(Xs, Xs):- !.                    % Don't need to redo after positive match
palindrome([X|Xs],Xs):- !.                 
palindrome([X|Xs],Ys):- palindrome(Xs, [X|Ys]). 
Typical trace for a palindrome:
[trace] 88 ?- pal([1,2,1]).
   Call: (6) pal([1, 2, 1]) ? creep
   Call: (7) pal([1, 2, 1], []) ? creep
   Call: (8) pal([2, 1], [1]) ? creep
   Exit: (8) pal([2, 1], [1]) ? creep         % Matches rule - palindrome([X|Xs],Xs).
   Exit: (7) pal([1, 2, 1], []) ? creep
   Exit: (6) pal([1, 2, 1]) ? creep
true .
And a non-palindrome:
[trace] 87 ?- pal([1,2,3]).
Call: (6) pal([1, 2, 3]) ? creep
   Call: (7) pal([1, 2, 3], []) ? creep
   Call: (8) pal([2, 3], [1]) ? creep
   Call: (9) pal([3], [2, 1]) ? creep
   Call: (10) pal([], [3, 2, 1]) ? creep
   Fail: (10) pal([], [3, 2, 1]) ? creep      % Fails as [] doesn't equal [3,2,1] and can't be pulled apart
   Fail: (9) pal([3], [2, 1]) ? creep
   Fail: (8) pal([2, 3], [1]) ? creep
   Fail: (7) pal([1, 2, 3], []) ? creep
   Fail: (6) pal([1, 2, 3]) ? creep
false.