Радио-86РК/Радио 04-87/Немного о программировании

Материал из Emuverse
Перейти к: навигация, поиск
Red copyright.png Данный материал защищён авторскими правами!

Использование материала заявлено как добросовестное, исключительно для образовательных некоммерческих целей.

Автор: Д. ГОРШКОВ, Г. ЗЕЛЕНКО

Источник: http://retro.h1.ru/RK86/Programm/LtlProgr.php

Те, кто читал журнал «Радио» в прошлом году, помнят наш заочный семинар «ЭВМ-СИСТЕМЫ-СЕТИ». В нем мы рассказывали об основах вычислительной техники и программирования. В "том году семинар продолжает работу под названием «ПК — ЯЗЫКИ — ПРОГРАММЫ». Мы расскажем о математическом обеспечении, о конкретных моделях персональных компьютеров. На первом занятии мы вспомним языки программирования и более подробно поговорим о языке ассемблера.

Все языки программирования можно разделить на две группы. К первой относится язык, непосредственно «понимаемый» микропроцессором — язык машинных кодов. Для каждого типа ЭВМ существует свой машинный язык, поэтому язык машинных кодов называют машинно-ориентированным.

Ко второй группе относятся более легкие для программиста языки высокого уровня — машинно-независимые языки.

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

По-другому обстоит дело с языками высокого уровня. В них каждую команду можно сравнить уже со словосочетанием, имеющим свой смысл.

Таким образом, языки высокого уровня предоставляют программисту как бы набор «штампов» — готовых стандартных средств, используемых для решения определенных задач. А это существенно упрощает решение задач, оберегает от ошибок, но ограничивает творческую инициативу. Поэтому появление языков высокого уровня существенно облегчило программирование, но во многих случаях программы стали менее эффективны.

Как же оценивается эффективность программ? Здесь мы рассмотрим только основные ее параметры: объем памяти, занимаемый программой, и время ее работы. Значения этих параметров стараются максимально уменьшить. Это необходимо для того, чтобы программа «умещалась» в памяти компьютера или занимала меньше в ней места, чтобы быстрее осуществлялся ее ввод или, если вы хотите записать ее в ПЗУ, чтобы для этого требовались меньшие по емкости и более дешевые микросхемы.

Программа должна «работать» быстро, чтобы не приходилось томительно долго ожидать у экрана результатов, чтобы компьютер мог управлять какими-либо подключенными к нему устройствами в «реальном масштабе времени».

Практически между моментом прихода информации от объекта и подачи на него от ЭВМ управляющего сигнала всегда проходит некоторое время. Это время обычно называют временем реакции управляющей программы или микропроцессорной системы в целом. Оно не должно превосходить некоторую заданную величину, выбор которой зависит от объекта управления, Например, чтобы управлять моделью железной дороги, программа должна реагировать на изменение состояния модели с запаздыванием на десятые доли или единицы секунд. Если же требуется принимать и дешифровать сигналы. переданные кодом Морзе, то скорость работы программы должна быть на порядок выше.

Языки высокого уровня ориентированы на различные области применения (например, Фортран — для решения математических задач, Форт — для программирования алгоритмов управления. Кобол — для решения экономических задач). Они имеют соответствующие «выразительные» средства, позволяющие получать эффективные программы. Поэтому важен правильный выбор языка программирования применительно к каждой конкретной задаче.

Эффективность программ существенно зависит от трансляторов — программ, осуществляющих перевод (трансляцию) исходного текста программы с языка высокого уровня в машинные коды. Именно трансляторы переводят языковые «штампы» в последовательность машинных команд.

Трансляторы относят к системным программам, которые как бы являются обязательными и неотъемлемыми частями компьютеров. Системные программы необходимы пользователям компьютере как инструменты при написании и отладке своих прикладных программ. Наличие трансляторов с одинаковых языков высокого уровня для компьютеров разных типов, в том числе для компьютеров с различной системой команд, позволяет писать прикладные программы, работающие на любой из этих машин. Это означает, что языки высокого уровня обеспечивают возможность работы одной программы на разных машинах, т, е. «переносимость» программ.

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

Интерпретатор поочередно просматривает строки программы в том порядке, в котором они должны выполняться, и вызывает нужные подпрограммы, входящие в состав интерпретатора. Подпрограммы тут же выполняются компьютером. Таким образом, в памяти компьютера постоянно должны находиться интерпретатор и исходный текст прикладной программы.

При использовании компиляторов скорость работы прикладных программ во много раз больше, чем при использовании интерпретаторов. Действительно, компилятор транслирует исходный текст прикладной программы только один раз — при «переводе» в машинные коды. Интерпретатору же приходится транслировать программу построчно при каждом ее выполнении, и время этой трансляции входит во время работы прикладной программы.

Рассмотрим, как соотносятся объемы памяти, необходимые для работы прикладных программ при использовании компиляторов и интерпретаторов. Учтем, что объем памяти для хранения текста строки программы на языке высокого уровня в среднем меньше того, который необходим для хранения соответствующих машинных кодов, полученных в результате трансляции. Очевидно, что при использование компилятора объем программы в машинных кодах будет больше объема исходного текста, и чем больше программа, тем больше будет эта разница,

Так как при работе с интерпретатором в памяти компьютера всегда находится текст прикладной программы и транслятор, то при малых исходных программах (меньших или сравнимых с объемом интерпретатора) объем требуемой памяти компьютера будет больше, чем для аналогичной программы в машинных кодах, полученной с помощью компилятора. Для прикладных программ, текст которых занимает больший, чем интерпретатор, объем памяти, их суммарный объем может оказаться меньше, чем объем кодов предварительно скомпилированной программы,

Следует заметить, что, используя, компилятор, можно выполнить трансляцию не на том компьютере, на котором программа будет работать, а на хорошо оснащенной инструментальной ЭВМ, которая имеет больший объем внутренней памяти, внешнюю память в виде накопителей с произвольным доступом к информации (обычно это накопители на гибких магнитных дисках — НГМД). Все это необходимо для того, чтобы можно было использовать сложное системное программное обеспечение (различные трансляторы, редакторы текстов и программ, отладчики программ), что повышает производительность труда программистов.

В качестве инструментального компьютера для «Радио-86РК» может быть использована ЭВМ СМ-1800, выполненная на том же микропроцессоре КР580ИК60А. Могут быть использованы и ЭВМ с другой системой команд, например СМ-4, Эта машина должна быть оснащена специальными программами кросс-обеспечением, позволяющим транслировать программы в машинные коды микропроцессора КР580ИК80А.

Чтобы наглядно представить выигрыш, который дают инструментальные машины, отметим, что компилятор с языка Паскаль для ЭВМ СМ-1800 занимает на гибком магнитном диске более 200 Кбайт и требует для своей работы не менее 48 Кбайт ОЗУ машины, куда отдельные части компилятора загружаются по мере надобности. Таким образом, работать на Паскале на «Радио-86РК» без инструментальной ЭВМ практически нельзя.

Полученные на инструментальных ЭВМ программы в машинных кодах могут работать на таких простых компьютерах, как, например, «Радио-86РК» или каких-либо микропроцессорных устройствах управления, память которых рассчитана на выполнение только одной программы.

Несмотря на повсеместное распространение языков высокого уровня, по занимаемому объему памяти и скорости работы во многих случаях более выгодны программы, написанные на языке ассемблера. Это машинно-ориентированный язык, программирование на нем по сути не отличается от программирования в машинных кодах, Но при этом язык ассемблера позволяет работать программисту, используя мнемонику команд, символьные переменные и автоматическое распределение памяти, избавляет его от ручной трансляции исходных текстов программ в машинные коды,

С чем же связано то, что ассемблерные программы более эффективны? Поясним это на таком примере. Компилятор, встретив в исходном тексте программы какую-либо арифметическую операцию над целыми числами, включает в состав объектного (машинного) кода соответствующие программы для выполнения арифметических операций. Но предположим, что эти программы могут выполнять действия не только с целыми числами. Следовательно, а объектном коде появляются «лишние», неиспользуемые коды, Эта избыточность привозит к нерациональному увеличению объема программы после трансляции, увеличивается время ее выполнения, А программы на языке ассемблера в принципе могут быть свободны от такой избыточности, так как сам язык очень близок к машинным кодам.

Далее мы рассмотрим некоторые приемы работы на языке ассемблера на примере игровой программы, аналогичной программе на Бейсике, описанной в статье А. Долгого «Игровые программы на Бейсике» 1. Вы убедитесь, насколько более эффективна ассемблерная программа. Особенно заметен выигрыш в скорости работы. Это важнейший параметр для игровых программ — ведь если машина надолго задумывается над ответом на действия оператора, то играть становится просто скучно.

Прежде всего рекомендуем ознакомиться с публикациями «Радиолюбителю о микропроцессорах и микро-ЭВМ»2, где описаны простейшие программы в машинных кодах. Затем, пользуясь распечаткой программы с комментариями и приведенным ниже описанием, советуем мысленно выполнить ее с карандашом в руке.

НИЖГР:  EQU     16H
ВЕРХГР: EQU     19H
ВЛЕВО:  EQU     8
ВПРАВО: EQU     18H
ВВЕРХ:  EQU     19H
ВНИЗ:   EQU     1AH
ЛИНО:   EQU     0E900H
ДО:     EQU     ЛИНО+37
КОД:    EQU     0F812H
ВВОД:   EQU     0F803H
ВЫВОД:  EQU     0F809H
ПРИНТ:  EQU     0F818H
СТАРТ:  LXI     H,СТРО
        CALL    ПРИНТ
M10:    CALL    ВВОД
        MOV     C,A
        SUI     30H
        JC      M10
        CPI     10
        JNC     M10
        CALL    ВЫВОД
        ADD     A
        MOV     B,A
        ADD     A
        ADD     B
        CMA
        INR     A
        MOV     B,A
        MVI     C,0FFH
        LXI     H,4000H
        DAD     B
        SHLD    ЗАДЕРЖ
СТАРТ1: LXI     H,СТР3
        CALL    ПРИНТ
        CALL    ГПСВ
        CALL    КОД
        CPI     0DH
        JNZ     СТАРТ1+6
        MVI     C,1FH
        CALL    ВЫВОД
        LXI     H,ЛИНО
        PUSH    H
        LXI     B,1
        MVI     E,60
        CALL    БОРТ
        PUSH    H
        LXI     H,ЛИНО+1536
        MVI     E,60
        CALL    БОРТ
        POP     H
        MVI     C,64
        MVI     E,25
        CALL    БОРТ
        POP     H
        MVI     E,25
        CALL    БОРТ
        LXI     H,СТР2
        CALL    ПРИНТ
        LXI     H,ЛИНО+1504
        LXI     B,0-64
        PUSH    H
        PUSH    B
        LHLD    ГОЛОВА
        SHLD    ХВОСТ
        JMP     M7
ЦИКЛ:   CALL    КОД
        LXI     H,0-1
        CPI     ВЛЕВО
        JZ      M0
        LXI     H,1
        CPI     ВПРАВО
        JZ      M0
        LXI     H,0-64
        CPI     ВВЕРХ
        JZ      M0
        LXI     H,64
        CPI     ВНИЗ
        JZ      M0
        XTHL
M0:     POP     B
        POP     D
        XCHG
        DAD     D
        PUSH    H
        PUSH    D
        MOV     C,M
        MVI     M,'O'
        XCHG
        LHLD    ГОЛОВА
        MOV     M,E
        INX     H
        MOV     M,D
        INX     H
        MOV     A,H
        CPI     ВЕРХГР
        JNZ     $+5
        MVI     H,НИЖГР
        SHLD    ГОЛОВА
        LHLD    ЗАДЕРЖ
M6:     DCX     H
        MOV     A,L
        ORA     H
        JNZ     M6
        MOV     A,C
        SUI     20H
        ORA     A
        JZ      M3
        CPI     5FH
        JNZ     СТАРТ1
M7:     LDA     ДЛИН
        STA     РОСТ
M1:     LXI     H,СТР1
        CALL    ПРИНТ
M9:     CALL    ГПСВ
        MOV     A,H
        CPI     38H
        JNC     M9
        MOV     A,L
        CPI     60H
        JNC     M9
        MOV     C,H
        CALL    ВЫВОД
        MOV     C,L
        CALL    ВЫВОД
        CALL    БАЙТ
        SUI     20H
        ORA     A
        JNZ     M1
        MVI     C,7FH
        CALL    ВЫВОД
M3:     LXI     H,РОСТ
        DCR     M
        JM      M5
        LXI     H,ДО
        MVI     A,3AH
M4:     INR     M
        CMP     M
        JNZ     ЦИКЛ
        MVI     M,30H
        DCX     H
        JMP     M4
M5:     INR     M
        LHLD    ХВОСТ
        MOV     E,M
        INX     H
        MOV     D,M
        INX     H
        MOV     A,H
        CPI     ВЕРХГР
        JNZ     $+5
        MVI     H,НИЖГР
        SHLD    ХВОСТ
        MVI     A,20H
        STAX    D
        JMP     ЦИКЛ
БОРТ:   MVI     M,'+'
        DAD     B
        DCR     E
        JNZ     БОРТ
        RET
ГПСВ:   LHLD    CB
        MVI     C,16
ГП0:    MOV     A,H
        DAD     H
        ANI     60H
        JPE     $+4
        INX     H
        DCR     C
        JNZ     ГП0
        SHLD    CB
        RET
СТРО:   DB      0DH,0AH
        DB      '*** П И Т О Н ***'
        DB      0DH,0AH
        DB      'СКОРОСТЬ (0..9)?',0
СТР1:   DB      7,1BH,'Y',0
СТР2:   DB      7,1BH,'Y',20H,3AH
        DB      ' ДЛИНА - 000 ',0
СТР3:   DB      1BH,'Y',20H+24,20H+32
        DB      ' НАЖМИТЕ <ВК>',0
ДЛИН:   DB      4
CB:     DW      8
ГОЛОВА: DW      1600H
ХВОСТ:  DS      2
РОСТ:   DS      1
ЗАДЕРЖ: DS      2

WRK:    EQU     0F75AH  ; РАБОЧАЯ ЯЧЕЙКА
БАЙТ:   LHLD    WRK
        MOV     A,M
        RET

В таблице представлена распечатка программы на языке ассемблера. Она состоит из шести полей. Собственно текст программы записан в полях с третьего по пятое, а первые два являются результатом работы транслятора — ассемблера. Первое поле заполняется адресами ячеек памяти, в которых хранятся машинные коды программы. Сами коды записываются во втором поле.

Мнемонический код команды записывается в четвертом поле, а в пятом — необходимые операнды (адреса переходов, символические наименования регистров и т. д.). Операнд может быть задан как в виде конкретного числа, так и в виде символического имени, арифметического или логического выражения. В выражениях может использоваться специальный символ «$» Его значение равно адресу строки, в которой он расположен.

Шестое поле всегда начинается со знака «;». Здесь помещают комментарии, поясняющие работу программы. Хотя они и не обязательны, но существенно помогают разобраться в тексте программы.

В третьем поле записывают символические имена — метки — в тех случаях, когда необходимо отметить начало фрагментов программы, к которым осуществляется переход из других ее частей по командам передачи управления и вызова подпрограмм. Кроме того, с помощью таких меток указывают адреса рабочих ячеек памяти, используемых для хранения констант или промежуточных результатов.

Символическим именам могут быть присвоены любые численные значения. Для этого в третьем поле строки записывают определяемое имя, в четвертом — EQU (сокращение от английского equivalent — равно), а в пятом поле указывают присваемое значение.

Сокращение «EQU» называется псевдооператором, так как несмотря на то, что он записывается в поле операции, как и мнемонические обозначения кодов команд, ему не соответствует никакой машинный код,

Существуют и другие псевдооператоры. Один из них — «ORG» — указывает начальный адрес размещения программы или ее части в памяти. Этот адрес указывается а поле операндов. Псевдооператоры «DB» и «DW» используются для записи в память 8- и 16-разрядных констант соответственно. Константы могут задаваться в виде чисел, символических имен и выражений, разделенных запятыми. Для записи в память алфавитно-цифровых символов в поле операндов можно записать требуемую последовательность символов, заключив их в апострофы.

Псевдооператор «DS» резервирует ячейки памяти для хранения данных во время работы программы. Количество ячеек указывается в поле операндов, их содержимое перед началом работы программы не определяется.

Псевдооператор «END» записывается в конце программы,

При трансляции ассемблер производит автоматическое распределение кодов программ и данных в памяти. Для этого он дважды «просматривает» исходный текст. Сначала определяет значения всех символических имен, используемых в программе, то есть составляет таблицу, содержащую их имена, соответствующие им значения и адреса памяти. При втором просмотре вычисляются значения операндов (с учетом составленной таблицы) и генерируются машинные коды.

В нашей игре с помощью четырех клавиш («<-", "->», "­ ", "¯ ") можно управлять движением по экрану «питона» — цепочки символов «О». В произвольных местах на экране располагаются «кролики», обозначенные символами «М». Цель игры — вырастить «питона» максимальной длины. Растет он после того, как съест «кролика». Если «питон» наползает на ограничительные бортики, нарисованные по периметру игрового поля или на самого себя, игра прекращается.

Контроллер ЭЛТ компьютера «Радио-86РК» настроен программой МОНИТОР на отображение 30 строк по 78 символов в каждой, однако для четкого отображения информации подпрограмма вывода на экран использует только 25 строк по 64 символа, оставляя края экрана затемненными. Для этого в ячейках экранной области ОЗУ, соответствующих затемненной области, записывают код 00Н,

Во многих программах для отображения информации удобнее и быстрее непосредственно заносить коды символов в экранную область, а не пользоваться стандартной подпрограммой вывода МОНИТОРа (именно так и сделано в рассматриваемой программе). Следует помнить, что экранная область ОЗУ расположена в памяти, начиная с ячеек 36D0H или 76D0H для 16-Кбайтной и 32-Кбайт-ной версий соответственно. Разность адресов ячеек экранной области ОЗУ для соседних по горизонтали знакомест экрана равна 1, а по вертикали — 78 (4ЕН — буква Н в конце числа означает, что оно приведено в шестнадцатеричной форме). Знакоместам, расположенным на экране правее или ниже, соответствуют большие адреса.

Приведенная в таблице программа рассчитана на 16-Кбайтный вариант компьютера — символическому имени ЛИНО присвоено значение 037С2Н (для 32-Кбайтного варианта следует присвоить значение 077С2Н).

Движение «питона» по экрану имитируется выводом очередного символа «О» в позицию, куда должна передвинуться его голова, и стиранием символа, соответствующего хвосту (поджимание хвоста). В памяти машины координаты всех символов (адреса экранной области, куда выводится символ «О»), составляющих тело «питона», записаны в экранной области в ячейках с адресами от 1400Н до 1700Н. Эту область памяти далее будем называть кольцевым буфером. Количество адресов, которое в нем можно хранить, существенно больше, чем длина «питона», выращенного даже самым удачливым игроком.

В кольцевом буфере хранятся не только настоящие, но и прошлые координаты питона. Ясно, что при долгой игре этих следов «питона» будет много. Может получиться, что памяти кольцевого буфера не хватит для того, чтобы хранить все бывшие координаты «питона». Поэтому после заполнения буфера сначала голова, а затем и весь «питон» переползает из конца в начало буфера.

У читателя может возникнуть вопрос: а зачем нам помнить старые координаты, тратить на это память? Можно, конечно, отвести под кольцевой буфер число ячеек, достаточное для хранения лишь координат максимально длинного «питона». Но тогда ему слишком часто придется «переползать» из конца в начало буфера, т, е. часто будет выполняться пересылка содержимого одной ячейки в другую. Это снизит скорость работы программы. Кроме того, памяти машины вполне достаточно для организации большого буфера.

Символьными переменными ГОЛОВА или ХВОСТ задают соответственно адреса начала и конца той области кольцевого буфера, где в текущий момент записаны адреса ячеек экранной памяти с кодами символа «О», то есть определяют местоположение «питона» на экране. Под эти переменные в памяти резервируют соответствующие пары (каждая точка на экране имеет двухбайтовый адрес) рабочих ячеек.

При движении «питона» его голова перемещается на следующую позицию экрана, а значение переменной ГОЛОВА всегда увеличивается на два, так как в кольцевой буфер заносится двухбайтовый адрес нового положения головы. Если «питон» не наполз при этом на «кролика», происходит «поджимание хвоста». Для этого из пары ячеек кольцевого буфера, адрес первой из которых определяется переменной ХВОСТ, считывается адрес ячейки экранной области, в которую записывается код 00Н, в результате чего гаснет последний символ а цепочке, изображающей «питона». Затем значение переменной ХВОСТ также увеличивается на два. При этом программа следит, чтобы значения переменных ГОЛОВА и ХВОСТ не превысили значения адреса верхней границы кольцевого буфера. При превышении переменными предельного значения им присваивается адрес нижней границы кольцевого буфера.

Длина «питона» увеличивается, если он наполз на «кролика» (М). В этом случае поджимание хвоста несколько раз не выполняется, в то время как голова продолжает перемещаться вперед. «Питон» удлиняется на величину, записанную в ячейке ДЛИН. После того, как «кролик» съеден, эта константа загружается в зарезервированную рабочую ячейку РОСТ. При движении головы значение переменной, хранимой а этой ячейке, уменьшается на 1. Процесс поджимания хвоста возобновляется, когда переменная РОСТ становится равной 0. Изменяя константу ДЛИН, можно менять степень сложности игры, замедляя или убыстряя рост «питона».

Длина «питона» отображается на экране в виде трехразрядного десятичного числа. Три смежные ячейки экранной области, в которых записано это число, используются следующим образом. Для отображения цифры 0 в них записывается код 30Н, для отображения 1 — 31Н, для 2 — 32Н и т. д. Таким образом, при увеличении длины «питона» код младшей цифры увеличивается на единицу, пока его значение не превысит 39Н, после чего туда записывается код цифры 0 — 30Н и увеличивается цифра в следующем разряде.

Направление движения «питона» задается 16-разрядной величиной смещения, хранимой в стеке. Эта величина суммируется с адресом ячейки памяти в экранной области, соответствующей голове «питона». Адрес хранится не только в кольцевом буфере, но и в стеке, что упрощает доступ к адресу. Таким образом получается адрес следующего положения головы. Например, в начале игры, когда питон до нажатия на управляющую клавишу ползет вверх, величина смещения равна 0-78 (в двоичном дополнительном коде OFFB2H. В строке 1169 это число в соответствии с принятой формой представления чисел записано в виде B2FF — два правых разряда переставляются в левые и наоборот. Если нажата управляющая клавиша "|", величина смещения заменяется на 1, и «питон» начинает ползти вправо, пока не будет нажата другая управляющая клавиша.

Переменная ЗАДЕРЖ определяет временную задержку, замедляющую работу программы. Дело в том, что без задержки программа работает слишком быстро, в отличие от ее аналога на Бейсике. Попробуйте поставить в ячейку ЗАДЕРЖ минимальную константу — 0001 Н, и вы увидите, что «питон» проползает экран за доли секунды. Меняя величину задержки, можно увеличивать или уменьшать скорость движения «питона».

Место на экране, в котором появляется очередной «кролик», должно выбираться случайно. Для этого необходимо генерировать случайные величины, что невозможно, так как компьютер работает по программе, а любая программа исключает случайность. Но можно составить программу генерации последовательности псевдослучайных величин.

В начале программы производится начальная установка генератора псевдослучайных величин, для чего подпрограмма генерации вызывается в цикле до тех пор, пока оператор не нажмет на какую-либо клавишу. Это позволяет каждый раз играть с разным расположением «кроликов».

После очистки экрана программа рисует ограничительную рамку, выводит сообщение о начальной длине «питона», устанавливает начальный адрес головы «питона» и направление его движения, а затем передает управление внутрь рабочего цикла программы — в ту его часть, где производится генерация нового «кролика».

Для задания положения «кролика» на экране используется прямая адресация курсора с указанием случайных номеров строки и позиции в строке. Однако перед выводом символа на экран следует проверить, свободна ли данная позиция от каких-либо других символов. Для проверки вызывается подпрограмма МОНИТОРа запроса байта из экранного буфера. Если данная позиция занята, символ не выводится на экран и происходит генерация новых номеров строки и позиции,

Если вы теперь введете в ОЗУ компьютера коды, представленные во втором поле распечатки в соответствии с адресами из первого поля, то сможете запустить игру на своем компьютере.

Д. ГОРШКОВ, Г. ЗЕЛЕНКО

г. Москва

  1. «РАДИО», 1987, № 2, 3
  2. «Радио», 1982, № 9-12

Отсканировано с журнала Радио № 4 1987 год.

Отредактировано Лесных Ю. 2001 год.