Voyager #03
19 октября 1998

Скелет - Недокументированная особенность процессора Z80.

<b>Скелет</b> - Недокументированная особенность процессора Z80.
(C) Иван Рощин, Москва 
Music: WORLD without LIMIT/KENOTRON 
───────────────────────────────────────────────────



      ╔════════════════════════════════════╗ 
      ║                              ║ 
      ║  Недокументированная особенность ║ 
      ║        процессора Z80         ║ 
      ║                              ║ 
      ╚════════════════════════════════════╝ 



1. С чего все началось. 
─────────────────────── 


     Писал я как-то очередную версию программы
BestView (v2.4) и использовал в ней вот такой фраг-
мент: 
       .... 


       EI 
       CALL    SUBR1 
       HALT 
       .... 


SUBR1  LD      A,R 
       PUSH    AF 
       DI 
       .... 


       POP     AF 
       DI 
       RET     PO 
       EI 
       RET 


     В этом фрагменте происходит вызов процедуры
SUBR1, которая на время своей работы запрещает
прерывания, а при выходе - восстанавливает прежний
режим их работы. 

     Проверка того, разрешены или запрещены пре-
рывания при вызове процедуры и восстановление ре-
жима прерываний происходит следующим образом: 

- команда LD A, R заносит во флаг P/V состояние
  триггера прерываний IFF2; 
- регистровая пара AF запоминается на стек (PUSH AF);
- запрещаются прерывания (DI); 
- выполняются, собственно те функции, для которых
  и предназначалась процедура SUBR1; 
- содержимое AF восстанавливается (POP AF); 
- прерывания запрещаются (DI); 
- если флаг P/V сброшен, происходит выход из про-
  цедуры с запрещенными прерываниями (RET PO); 
- иначе происходит выход с разрешенными прерывани-
  ями (EI: RET). 


     Я стал замечать, что при работе этого фраг-
мента BestViev зависает - не всегда и даже не слиш-
ком часто, а в очень редких случаях. Но, все равно,
это было не очень-то приятно. Программа, вроде бы,
не содержала никаких ошибок, по крайней мере с пер-
вого взгляда ничего подозрительного я не заметил.
Оставалось лишь прибегнуть к более сильным средс-
твам... 


2. Ситуация начинает проясняться. 
────────────────────────────────── 


     После очередного зависания я вставил чистый
диск и уверенно нажал кнопку MAGIC. Затем загрузил
отладчик "STS 6.2+@" (не зря я его переделывал -
теперь с его помощью после загрузки @-файла можно
восстановить содержимое регистров процессора на
момент сброса программы на диск). Нажатие пары кла-
виш - и вот я вижу, в каком месте программы про-
изошло зависание. 

      .... 


      EI 
      CALL     SUBR1 
      HALT <──────────── вот здесь! 
      .... 


     Типичный случай - прерывания запрещены, и про-
цессор прекратил выполнение программы на команде
HALT. Но почему прерывания оказались запрещены -
неясно. Ведь перед вызовом процедуры SUBR1 они бы-
ли разрешены командой EI, а после окончания работы
SUBR1, они тоже должны быть разрешены - процеду-
ра SUBR1 не должна оказывать влияния на режим их
работы. 
     Трассирую SUBR1. Все идет как положено - и
при входе, и при выходе прерывания остаются разре-
шенными. Повторяю трассировку: раз, другой, ... деся-
тый. Все идет нормально. 
     А может быть, дело в том, что в SUBR1 что-
то происходит со стеком? И из-за этого иногда не-
правильно восстанавливается содержимое AF? Надо бы
проверить... 


3. Зависания: дубль второй. 
─────────────────────────── 


     Ну вот, переделал программу. Теперь уж точно
буду знать, в чем дело: 

SUBR1 LD      A,R 
      PUSH    AF 
      DI 


      PUSH    HL 
      PUSH    AF 
      POP     HL 
      LD      (WR_HH1),HL 
      POP     HL 
      .... 


      POP     AF 


      PUSH    HL 
      PUSH    AF 
      POP     HL 
      LD      (WR_HH2),HL 
      POP     HL 


      DI 
      RET     PO 
      EI 
      RET 


WR_HH1 DW     0 
WR_HH2 DW     0 


     Содержимое AF теперь запоминается не только
в стеке, но и в переменной WR_HH1 (для контроля),
а при выходе из процедуры - снятое со стека значе-
ние запоминается в WR_HH2. Если процедура работа-
ет правильно, WR_HH1 и WR_HH2 должны совпадать,
а флаг P/V - быть установленным. 

     Запускаю... Вот уже минуту BestView работает
нормально... Просматриваю с ее помощью тот самый
файл, при просмотре которого она зависла в прошлый
раз... Ну вот, опять! Да и неудивительно, ведь причи-
ну я не устранил. Ладно, будем разбираться. 

     Снова нажимаю MAGIC, загружаю "STS 6.2 + @"
и сразу же проверяю значения WR_HH1 и WR_HH2. И
там, и там записано #5908. Значения совпадают -
следовательно, при работе со стеком ошибок не бы-
ло. Но если в регистре флагов содержится #08, зна-
чит, флаг P/V сброшен, и при вызове процедуры SUBR1
прерывания были запрещены. 


     Но это же совершенно невозможно! Ведь в про-
грамме стоит EI: CALL SUBR1! Наверное, просто Спек-
трум перегрелся, и потому такие глюки. Ничего более
умного я в этот день так и не придумал. 



4. Ложный след. 
─────────────── 


     На следующий день я нашел возможное объясне-
ние таинственному запрещению прерываний. Допустим,
после команды EI, но до выполнения команды LD A,R
произошло прерывание. Как известно, процедура его
обработки должна заканчиваться командами EI: RET
(потому, что в начале обработки происходит автома-
тическое запрещение прерываний). Если же обработчик
прерываний завершается просто командой RET, то пре-
рывания останутся запрещенными. 


     Конечно, вероятность того, что прерывание про-
изойдет именно между командами EI и LD A,R очень
мала, но ведь и зависания происходят очень редко.
Так что, это лишний раз подтверждало мою гипотезу.

     Тем не менее, оставалось неясным, с чего бы
это обработчик прерываний завершался командой RET,
а не EI: RET. Я решил проверить, действительно ли
в этом все дело, и для этого добавил после EI ко-
манду HALT (см. ниже). Если обработчик прерываний
действительно завершается некорректно, то после
добавленного HALT'а прерывания всегда будут запре-
щены, и, соответственно, BestView всегда будет за-
висать. 
      .... 


      EI 
      HALT <─────────── добавленная команда 
      CALL    SUBR1 
      HALT 
      .... 


SUBR1 LD      A,R 
      PUSH    AF 
      DI 
      .... 


      POP     AF 
      DI 
      RET     PO 
      EI 
      RET 


     Компилирую, запускаю... Совершенно неожиданный
результат - зависания полностью прекратились! Хотел
все так и оставить, но все же решил разобраться,
почему так получается. 


5. Прием "упрощение программы". 
──────────────────────────────── 


     Когда найти ошибку обычными средствами не
удается, я удаляю из программы все, что можно, но
чтобы ошибка при этом оставалась. В итоге, когда
от программы остается с десяток строк, ошибка за-
метна сразу. Так я поступил и в этот раз: 

      ORG      #6000 


      EI 


M1    CALL    SUBR1 
      JR      M1 


SUBR1 LD      A,R 
      DI 
      JP      PO,M2 
      EI 
      RET 
M2    LD      A,4 
      OUT     (254),A 
      RET 


     Вот такая программа, всего 19 байт. Разреша-
ются прерывания, и в бесконечном цикле вызывается
процедура SUBR1. Эта процедура устанавливает зеле-
ный border, если при входе в нее прерывания были
запрещены и не меняет цвет border, если прерывания
разрешены. Таким образом, если произойдет самопро-
извольное запрещение прерываний, это сразу же бу-
дет заметно. 

     Запускаю - да, border меняет свой цвет на зе-
леный. Причина столь странного поведения программы
остается неизвестной. Может быть, в этом виновата
процедура обработки прерываний 1-го рода? Добавляю
к программе несколько команд, устанавливающих ре-
жим IM2 с обработчиком, состоящим всего из двух
команд: EI: RET. 


      ORG     #6000 


      LD      HL,#8000 
      LD      (HL),#81 
      LD      DE,#8001 
      LD      BC,#100 
      LDIR 
      LD      A,#80 
      LD      I,A 
      IM      2 


      EI 


M1    CALL    SUBR1 
      JR      M1 


SUBR1 LD      A,R 
      DI 
      JP      PO,M2 
      EI 
      RET 
M2    LD      A,4 
      OUT     (254),A 
      RET 


      ORG     #8181 


      EI 
      RET 


     Запускаю - тот же результат! Хотя при трас-
сировке и этой, и предыдущей программы в отладчи-
ке, border остается черным. Внимательное изучение
программы приводит к предположению: может быть, ко-
манда LD A,R иногда устанавливает бит P/V так, как
будто прерывания запрещены, в то время, как на са-
мом деле они разрешены? 



6. Неужели ошибка в процессоре? 
───────────────────────────────── 


     Еще раз изменяю программу. Теперь прерывания
вообще не будут запрещаться (убрана команда DI). Ес-
ли при выполнении команды LD A,R бит P/V станет
равным 0, на некоторое время border станет зеленым
(для этого предусмотрена задержка): 

      ORG     #6000 


      LD      HL,#8000 
      LD      (HL),#81 
      LD      DE,#8001 
      LD      BC,#100 
      LDIR 
      LD      A,#80 
      LD      I,A 
      IM      2 


      EI 


M1    CALL    SUBR1 
      JR      M1 


SUBR1 LD      A,R 
      RET     PE 


      LD      A,4 
      OUT     (254),A 
      LD      HL,0 
      LD      DE,0 
      LD      BC,#600 
      LDIR             ;WAIT 
      XOR     A 
      OUT     (254),A 
      RET 


      ORG     #8181 


      EI 
      RET 


     Запускаю... И что я вижу? Верхняя часть bor-
der'а мигает зеленым цветом: 


       ┌─────────────────────────────────┐ 
       │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ 
       │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ 
       │▒▒▒▒                     ▒▒▒▒│ 
       │▒▒▒▒                     ▒▒▒▒│ 
       │                           │ 
       │                           │ 
       │                           │ 
       │                           │ 
       │                           │ 
       │                           │ 
       └─────────────────────────────────┘  


     Это говорит о том, что, во-первых, команда
LD A,R действительно иногда неверно устанавливает
бит P/V, и во-вторых, что это происходит в момент
прихода прерывания, а не когда угодно (действитель-
но, тогда бы border мигал зеленым в совершенно про-
извольных местах). 


     То, что верхняя часть border'а мигает, а не
постоянно окрашена в зеленый цвет, тоже получает
свое объяснение. По-видимому, команда LD A,R непра-
вильно работает лишь тогда, когда импульс прерыва-
ния приходит во время ее выполнения, а так бывает
далеко не всегда - прерывание может произойти и во
время выполнения другой команды. 



          * ПРОДОЛЖЕНИЕ СЛЕДУЕТ * 




───────────────────────────────────────────────────
 ому, команда LD A,R непра-
вильно работает лишь тогда, когда импульс прерыва-
ния приходит во время ее выполнения, а так бывает
далеко не всегда - прерывание может произойти и во
время выполнения другой команды. 



          * ПРОДОЛЖЕНИЕ СЛЕДУЕТ * 




(C) Иван Рощин, Москва 
Music: BURATINO remixed by KENOTRON 
───────────────────────────────────────────────────


    ╔═════════════════════════════════════╗ 
    ║                               ║ 
    ║  Недокументированная особенность  ║ 
    ║         процессора Z80         ║ 
    ║           (окончание)           ║ 
    ║                               ║ 
    ╚═════════════════════════════════════╝ 



7. Окончательное подтверждение. 
──────────────────────────────── 


     Проверим этот факт. Пусть обработчик преры-
вания определяет, в каком месте была прервана про-
грамма. Если она была прервана именно после коман-
ды LD A,R, пусть бордюр на некоторое время станет
желтым: 

      ORG     #6000 


      LD      HL,#8000 
      LD      (HL),#81 
      LD      DE,#8001 
      LD      BC,#100 
      LDIR 
      LD      A,#80 
      LD      I,A 
      IM      2 


      EI 


M1    CALL    SUBR1 
      JR      M1 


SUBR1 LD      A,R 
BP1   RET     PE 


      LD      A,4 
      OUT     (254),A 
      LD      HL,0 
      LD      DE,0 
      LD      BC,#600 
      LDIR             ;WAIT 
      XOR     A 
      OUT     (254),A 
      RET 


      ORG     #8181 


      EXX 
      EX      AF,AF' 


      POP     HL 
      PUSH    HL 
      LD      DE,BP1 
      AND     A 
      SBC     HL,DE 
      JR      NZ,NE_BP1 


      LD      A,6 
      OUT     (254),A 
      LD      HL,0 
      LD      DE,0 
      LD      BC,#600 
      LDIR             ;WAIT 


NE_BP1EXX 
      EX      AF,AF' 


      EI 
      RET 


     Если неправильная работа команды LD A,R не
связана с тем, что во время ее выполнения приходит
импульс прерывания, то мы увидим, как верхняя часть
border'а будет мигать то зеленым цветом, то жел-
тым. Но если связь между этими двумя событиями
существует, то мы должны увидеть, как верхняя
часть border'а мигает желтым цветом, а нижняя
часть - зеленым, причем, они должны мигать совер-
шенно синхронно. 


     Запускаю - и вижу именно то, что и предпола-
гал. Действительно, такая связь существует: 

       ┌─────────────────────────────────┐ 
       │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ 
       │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ 
       │▓▓▓▓                     ▓▓▓▓│ 
       │▓▓▓▓                     ▓▓▓▓│ 
       │▒▒▒▒                     ▒▒▒▒│ 
       │▒▒▒▒                     ▒▒▒▒│ 
       │▒▒▒▒                     ▒▒▒▒│ 
       │▒▒▒▒                     ▒▒▒▒│ 
       │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ 
       │                           │ 
       └─────────────────────────────────┘ 


     Но как могут быть связаны прерывания и рабо-
та команды LD A,R? Эта команда помещает во флаг
P/V содержимое триггера прерываний IFF2. При разре-
шенных прерываниях этот триггер равен 1, а когда
приходит импульс прерывания, он автоматически сбра-
сывается в 0, чтобы исключить повторную обработку
прерывания. Но обработка запроса на прерывание на-
чинается во время выполнения последнего такта вы-
полняемой команды (т.е. команды LD A,R). И, видимо,
уже сброшенный триггер IFF2 копируется во флаг
P/V (действительно, с точки зрения процессора, пре-
рывания в этот момент уже запрещены). 


     Все сказанное относится и к команде LD A,I.
Приведенная информация была проверена на оригиналь-
ном процессоре Z80 фирмы ZILOG, и на отечествен-
ном аналоге КР1858ВМ1. 



8. К чему это приводит и что делать? 
─────────────────────────────────────── 


     Применение команд LD A,R и LD A,I для опре-
деления состояния триггера прерываний, вообще гово-
ря, используется во многих программах (и даже в
ПЗУ TR-DOS). Вот вам и объяснение некоторого чис-
ла странных зависаний. Кажется, будто вероятность
прихода импульса прерывания именно во время выпол-
нения команды LD A,R невелика. Но, во-первых, веро-
ятность увеличивается за счет того, что эта коман-
да может выполняться в программе не один раз (а
для зависания достаточно единственного неверного вы-
полнения), и, во-вторых - если в программе до это-
го встречалась команда HALT, т.е. синхронизация с
прерываниями, может случиться так, что команда
LD A,R будет каждый раз выполняться в то время,
когда наиболее вероятно очередное прерывание (так
и было в BestView). 


     Итак, этот способ ненадежен. Но как же быть?
Оказывается, можно со 100% точностью определять
состояние триггера прерываний по следующему прос-
тому правилу: 


 - выполнить команду LD A,R; 
 - если флаг P/V = 1 - значит, прерывания в самом
   деле разрешены; 
 - если флаг P/V = 0 - либо прерывания в самом де-
   ле запрещены, либо они разрешены, но команда
   LD A,R неверно установила флаг. 


Чтобы устранить неопределенность, еще раз выполня-
ем команду LD A,R. Если и теперь флаг P/V = 0 -
значит, прерывания запрещены (в самом деле, не мо-
жет быть так, чтобы и во время выполнения второй
команды LD A,R произошло прерывание - между двумя
прерываниями проходит 1/50 секунды, а между двумя
командами LD A,R - гораздо меньше времени). 
Если же флаг P/V = 1 - значит, прерывания разреше-
ны. 


     Вот соответствующий фрагмент программы: 

SUBR1 LD      A,R 
      JP      PE,M1 
      LD      A,R 
M1    PUSH    AF 
      DI 
      .... 


      POP     AF 
      DI 
      RET     PO 
      EI 
      RET 



9. Как это можно использовать? 
──────────────────────────────── 


     С помощью команды LD A,R удобно выполнять
тестирование процессора, чтобы распознать выполне-
ние программы под эмулятором. Эмулятор выполняет
команды Z80 последовательно, одну за другой, и ко-
манда LD A,R всегда будет правильно устанавливать
флаг P/V. А в реальном Z80 это не так. 


     Вот простейшая процедура для тестирования
процессора, которая возвращает в аккумуляторе 1,
если она запущена на реальном Z80, и 0 в против-
ном случае. Она пытается 65536 раз прочитать ре-
гистр R при разрешенных прерываниях, и если при
этом хотя бы один раз флаг P/V установится в 0 -
делается вывод, что процедура работает на реальном
Z80. 


TESTZ80 EI 
        LD      BC,0 
M1      LD      A,R 
        JP      PO,QUIT 
        INC     BC 
        LD      A,B 
        OR      C 
        JR      NZ,M1 
        RET 
QUIT    LD      A,1 
        RET 


     Если распознался эмулятор, можно либо прекра-
щать выполнение программы (своеобразная защита),
либо отключать некоторые участки программы, кото-
рые могут неверно работать под эмулятором (напри-
мер, вместо прямой работы с ВГ93 использовать точ-
ку входа #3D13 и т.п.). 


10. Исправление STS 6.2. 
───────────────────────── 


     В известном отладчике STS определение состо-
яния триггера прерываний также происходит с помо-
щью команды LD A,R. Из-за этого может неправильно
выполняться трассировка программы. При трассировке
STS запускает каждую команду (кроме команд переда-
чи управления) с помощью резидента, а после оконча-
ния ее выполнения запоминает содержимое регистров
процессора и состояние триггера прерываний. Вот тут
и возможны ошибки. 

     Допустим, прерывания разрешены и трассирует-
ся такая простейшая программа: 

      #8000      NOP 
      #8001      JR      #8000 


     Прекратив трассировку через некоторое время
(при отключенной опции Indicate одной минуты впол-
не достаточно), мы увидим, что прерывания оказались
запрещены. Если бы трассировалась реальная програм-
ма, такое запрещение прерываний могло оказать вли-
яние на весь ход ее дальнейшего выполнения и даже
привести к зависанию (если при трассировке попалась
бы команда HALT). 


     Совершенно очевидно, что в STS надо внести
исправления. Вот как это сделать для версии 6.2: 


     Сначала нужно запустить STS и загрузить файл
"sts6.2 <C>", в котором и будут производиться ис-
правления. 


     Затем нужно отыскать свободные 14 байт - их
назначение будет объяснено ниже. Можно использовать
буфер функции пользователя (с адреса #FE37). Но в
той версии STS, которую я использую, этот буфер
занят под процедуру дизассемблирования с метками
ассемблера ZX ASM, поэтому я решил сократить не-
которые текстовые сообщения: 


    'Block'    -> 'Bl.'   (экономится 2 байта) 
    'Save'     -> 'S.'    (------/------ 2 ---/---) 
    'Load'     -> 'L.'    (------/------ 2 ---/---) 
    'DEFB'    -> ' '    (------/------ 4 ---/---) 
    'FileName'  -> 'Name' (------/------ 4 ---/---) 


     Для этого с адреса #EB24 нужно ввести сле-
дующую последовательность байт: 


    #EB24: AE 46 72 6F ED 54 EF      46 
    #EB2C: 69 6C E5 53 65 63 74      6F 
    #EB34: F2 53 AE 4C AE 53 74      6F 
    #EB3C: 70 20 69 E6 42 61 6E      EB 
    #EB44: 51 75 69 F4 54 72 61      63 
    #EB4C: E5 53 74 61 72 F4 44      69 
    #EB54: 73 61 73 ED A0 46 69      6C 
    #EB5C: E5 42 41 53 49 C3 20      44 
    #EB64: 4F D3 


     По адресу #E702 заменяем значение #0A на
#0E, чтобы правильно печаталось имя файла (так как
вместо строки FileName осталось просто Name). 


     Итак, теперь с адреса #EB66 свободно 14 байт.
Смотрим, где в STS происходит определение состоя-
ния триггера прерываний: 


   #DFFE:    LD    (#5BA1),SP 
            LD    SP,#5BA1 
            PUSH  BC 
            PUSH  AF 
            LD    A,R 
            DI 
            LD    BC,#7FFD 
            LD    A,#1F 
            OUT   (C),A 
            LD    B,#BF 
            LD    A,#00 
            OUT   (C),A 
            JP    #E028 


     Заменим команды LD A,R: DI на NOP, а коман-
ду JP #E028 - на JP #EB66. С адреса #EB66 поме-
стим такой фрагмент: 


   #EB66:    LD    A,R 
            JP    PO,#EB6E             NOP                   ┌─  JR    #EB70   LD    A,R    <──┘ 
        └─>  DI 
            JP    #E028 


     Обратите внимание - этот фрагмент в любом
случае при своей работе увеличивает регистр R на
одинаковую величину (на 7). Дело в том, что далее
будет выполнена еще одна команда LD A,R, на этот
раз нужная уже для определения значения регистра R,
и будет произведена коррекция полученного значения,
т.к. значение регистра R увеличивается с каждой вы-
полненной командой, а надо узнать его значение на
момент окончания выполнения трассируемой команды.
Вот как это выглядит: 


   #DCA2:    LD    A,#5A  
            LD    HL,#FEF4 
            SLA   (HL) 
            RLA 
            ADD   A,(HL) 
            RRCA 
            LD    (HL),A 
            RET 


     Константу #5A по адресу #DCA3 следует заме-
нить на #53, т.е. уменьшить на 7 - ведь в програм-
му был добавлен дополнительный фрагмент, увеличива-
ющий регистр R на 7, и надо скомпенсировать это
изменение. 


     После этого остается только записать изменен-
ный файл на диск. 



───────────────────────────────────────────────────
 ь в програм-
му был добавлен дополнительный фрагмент, увеличива-
ющий регистр R на 7, и надо скомпенсировать это
изменение. 


     После этого остается только записать изменен-
ный файл на диск. 






Другие статьи номера:

Информбюро - О планах редакции журнала.

Авторы - Авторы журнала и контакты редакции.

Информбюро - Путеводитель по журналу.

Лоцман - О игре SubSunk.

Лоцман - Описание игры DARK SIDE.

Лодырь - Полное прохождение игры ЗЕРКАЛО.

Калейдоскоп - О игре Пыль звездных дорог.

Калейдоскоп - О втором виртуальном фестивале компьютерного искусства Art Comp-99.

Калейдоскоп - Презентация текстовой утилиты - Text Utility V2.8i.

Скелет - Описание звуковой карты DMA SOUND.

Скелет - Недокументированная особенность процессора Z80.

История - Жизнь после Вилли. Софтография Мэтью Смита.

История - О фидошном слэнге.

Лаборатория - интро POORGUY! - изнутри (исходный текст с комментариями).

Лаборатория - ZX-SPECTRUM и операционные системы.

Лаборатория - Процедуры от MicroSoft.

Лаборатория - Алгоритм вывода десятичных чисел на экран монитора из регистра DE & HL.

Лаборатория - Алгоритм деления и умножения больших чисел.

PROFI CLUB - Изучаем CP/M (совместимость, процессоры, терминалы).

Юмор - Лесенка - советы грамотному юзеру.

Юмор - Стих "OA программистов".

Пишите письма - Письма читателей в журнал.

Реклама - Реклама и объявления ...

ZXTraders - Действyющие распространители программного обеспечения ZX Spectrum по России и Украине


Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Обзор ПО - Обзор новинок: Monstrland
Фантастика - Г.Гарисон (продолжение, часть 4-6).
События - NICRON'у ровно год!

В этот день...   21 января