A simple FIFO buffer in verilog
Your read pointer and write pointer both need to travel in the same direction.  Generally they are incremented, but decrementing also works, so long as you do the same for both.  One method for full/empty detection is to use M+1 bit registers for the read and write pointer.  If all M+1 bits match, then the FIFO is empty.  However, if all bits match except the MSB, then the FIFO is full.  Presuming you use a true dual port memory for storage (i.e. an FPGA block RAM), no locking is necessary as dual port RAM supports simultaneous reading and writing.  No need for any isEmpty and isFull registers or a lastOp register if you can glean all of that information from the read and write pointers with simple combinatorial logic.  However, you will need logic to prevent the write pointer from being changed when the FIFO is full or the read pointer from being changed when the FIFO is empty.