They are given a functioning compiler (written in Standard ML) for a small subset of a relatively simple artificial language (a new one every year), and they have to extend the compiler to support the full language. This involves changing every level (lexing, parsing, type-checking and code generation). For the final exam, they have to add a new feature to the language.
When I did this course, you had to implement a compiler for a toy language. The language would change each year. Our compiler was for a language called "FUN". I think it was a first-order functional language with heap-allocated objects. It had to be implemeted in SML, which comes with Lex and Yacc implementations. You got various bits and pieces handed out, like register-allocation and the assembly-generator (for MIPS).
The neat thing was, that that you also had to design a simple pipelined MIPS processor and you could run (in simulation) the code your compiler generated on the architecture. This gave you a pretty good understanding of what happens from the time you write your code to it actually generating a result.