Почему 2 раза используется команда «push ax»?
Обрати внимание, что двум вызовам push ax соответствуют два вызова pop ax.
Push сохраняет регистр на стеке, смещая вершину стека. Pop выталкивает значение с вершины стека в регистр и смещает вершину назад. Поэтому паттерн push-use-pop встречается довольно часто, если нам нужен регистр, но мы не хотим терять его предыдущее значение. 
В твоем случае этот паттерн встретился дважды, один внутри другого. Внешний сохраняет регистр при входе в подпрограмму, и восстанавливает при выходе (чтобы поменьше мешать вызывающей подпрограмме). Внутренний - сохраняет регистр AX перед пересылкой, так как мы не можем напрямую писать в ES, только через другой регистр.
push ax ; 2 раз
 mov ax,0B800h 
 mov es,ax 
 mov di,0 
 mov cx,2000 
 pop ax
здесь регистр ax используется временно, для загрузки в значения в регистр es