...

суббота, 4 апреля 2015 г.

Реактивное программирование в табличном процессоре


Табличный процессор (речь идет о MS Excel или LibreOffice Calc) — это довольно занятный и универсальный инструмент. Мне часто приходилось (и приходится) пользоваться его широкими возможностями: автоматизированные отчеты, проверка гипотез, прототипирование алгоритмов. Например, я использовал его для решения задач проекта Эйлер, быстрой проверки алгоритмов, реализовал парсер одного прикладного протокола (по работе надо было). Мне нравится наглядность, которую можно добиться в табличном процессоре, а еще мне нравится нестандартное применение всего, чего только возможно :) На Хабре уже появлялись интересные статьи на тему нестандартного применения Excel:

http://ift.tt/1DlPlF4

http://ift.tt/XVwuhX

http://ift.tt/WVfy7w

В этой длинной статье я хочу поделиться своими экспериментами в реактивном программировании с помощью формул табличного процессора. В результате этих экспериментов у меня получился «компьютер» с процессором, памятью, стеком и дисплеем, реализованный внутри LibreOffice Calc при помощи одних только формул (за исключением тактового генератора), который можно программировать на неком подобии ассемблера. Затем, в качестве примера и proof-of-concept, я написал игру «Змейка» и бегущуюползущую строку для этого компьютера.





Началось все с того, что я заинтересовался различными парадигмами программирования, посетил вводное занятие по Verilog в клубе робототехники; и вот в статье на википедии по реактивной парадигме я наткнулся на следующий текст:


Современные табличные процессоры представляют собой пример реактивного программирования. Ячейки таблицы могут содержать строковые значения или формулу вида «=B1+C1», значение которой будет вычислено исходя из значений соответствующих ячеек. Когда значение одной из зависимых ячеек будет изменено, значение этой ячейки будет автоматически обновлено.





Действительно, любой кто пользовался формулами в Excel знает, что изменив одну ячейку мы меняем связанные с ней ячейки — получается довольно похоже на распространение сигнала в цепи. Все эти факторы и навели меня на следующие мысли: а что если эта «цепь» будет достаточно сложной? являются ли формулы в табличном процессоре Тьюринг полными? можно ли «запрограммировать» формулы, так чтобы получить какие-нибудь нетривиальные результаты? (например сделать тетрис) Т.к. последнее время я использую Ubuntu на работе и дома, то все эксперименты я проводил в LibreOffice Calc 4.2.7.2

Начал эксперименты я с реализации дисплея. Дисплей представляет из себя набор квадратных ячеек 8х8. Здесь пригодилось условное форматирование (оно есть и в Excel и в Calc). Выделяем ячейки, заходим в Format/Conditional Formatting/Condition… и настраиваем внешний вид: черный фон, при условии, что в ячейке содержится, например, пробел. Теперь если записать в ячейку пробел, то она становится черной. Таким образом реализуются пиксели нашего дисплея. Но этим дисплеем хочется как-то управлять. Слева от него я выделил специальный столбец в который будут заноситься числа — идея такая, чтобы этим числом мы задавали битовую маску для отображения на экране. Сверху экрана я пронумеровал столбцы. Теперь в каждую ячейку дисплея мы должны написать формулу, которая даст в результате либо пробел, либо пустую строку, в зависимости от того, установлен ли нужный бит в самом левом столбце.

=IF(MOD(TRUNC(<битовая маска>/(2^<номер столбца дисплея>));2);" ";"")



Здесь, по сути, происходит сдвиг вправо (деление на степень двойки и потом отброс дробной части), а затем берется 0-й бит, то есть остаток от деления на 2, и если он установлен, то возвращается пробел, иначе пустая строка.

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

Для генерации была создана еще одна конструкция 8х8, в которую руками заносятся единицы, а формула сворачивает все это в одно число:

=SUMPRODUCT(<строка ячеек с единичками и ноликами>;2^<строка с номерами позиций>)



В итоге получил такую матрицу битовых масок для цифр:
Sign-generator


0 0 24 36 36 36 36 24 0
1 0 8 24 40 8 8 8 0
2 0 24 36 4 8 16 60 0
3 0 24 36 8 4 36 24 0
4 0 12 20 36 60 4 4 0
5 0 60 32 56 4 4 56 0
6 0 28 32 24 36 36 24 0
7 0 60 4 8 16 16 16 0
8 0 24 36 24 36 36 24 0
9 0 24 36 36 28 4 24 0



Здесь каждая строка соответствует десятичной цифре. Возможно, не самые красивые цифры вышли, к тому же, верхний и нижний ряд не использован, ну уж как нарисовал, так нарисовал )




Далее применим функцию INDEX, если ей указать матрицу, ряд и колонку, то она возвращает значение из этой матрицы. Так что, в каждой ячейке битовой маски дисплея пишем формулу

INDEX(<матрица>; <цифра> + 1; <номер строки дисплея>+1)



единицы прибавляются потому, что INDEX считает координаты с единицы, а не с нуля.

Что ж, дисплей готов, пишешь руками цифру — она отображается. Далее мне захотелось сделать так, чтобы цифра сама переключалась, то есть некий счетчик, который будет накапливать сумму. Здесь то и пришлось вспомнить про циклические ссылки в формулах. По-умолчанию, они выключены, заходим в опции, разрешаем циклические ссылки, я у себя настроил вот так:
Опции вычислений





Циклическая ссылка подразумевает под собой формулу в ячейке, зависящую от нее самой же, например, в ячейку A1 мы запишем формулу "=A1+1". Такая ячейка, конечно, не может быть вычислена — когда заканчивается число допустимых итераций, то Calc выдает либо #VALUE, либо ошибку 523. К сожалению, обмануть Сalc не удалось, идея была такая, чтобы сделать одну ячейку постоянно растущей до какого-то предела, например, в A1 я бы записал что-то вроде: =IF(A1
Cчетчик от 0 до 9



























AB
1Reset0
2Clock[меняется макросом 0 или 1]
3Old value=IF(B1=1; 0; IF(B2 = 0; B4; B3))
4New value=IF(B1 = 1; 0; IF(AND(B2 = 1; B4 = B3); IF(B4<9; SUM(B4;1); 0); B4))





Здесь уже предусмотрен сброс для инициализации начальных значений, путем занесения 1 в A1.

Такой счетчик подключается к дисплею из предыдущего раздела, и получается то, что видно на данном видео:

Счетчик + дисплей 8х8






Жаль, что не получилось обойтись полностью без макросов и тактовый генератор сделать на формулах не получилось. Кроме этого, возникла еще одна проблема: когда макрос зациклен — он блокирует основной поток, и ничего уже сделать нельзя, приходится завершать работу Calc. Но у меня уже зрели мысли об интерактивности, хотелось как-то управлять своей будущей схемой, например, сбрасывать все в ноль, или менять какие-то режимы во время работы.

К моему счастью, оказалось, что в Calc можно сделать так, чтобы основной поток макроса не блокировался. Здесь я немного слукавил и просто «нагуглил» готовое решение, приспособив его под себя. Это решение требовало Bean Shell для LibreOffice. Пакет называется libreoffice-script-provider-bsh. Код состоит из 2х частей: одна на BeanShell, другая на LibreOffice Basic. Честно говоря, полностью в коде я не разобрался… каюсь (не владею Java, BeanShell, да и с объектной моделью LibreOffice не особо знаком), но кое-что все-таки подправил.

BeanShell часть


import com.sun.star.uno.Type;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.lib.uno.helper.PropertySet;
import com.sun.star.lib.uno.helper.WeakBase;
import com.sun.star.task.XJobExecutor;
import com.sun.star.lang.XInitialization;
import com.sun.star.beans.PropertyValue;
import com.sun.star.beans.XPropertyChangeListener;
import com.sun.star.beans.PropertyChangeEvent;
import com.sun.star.lang.EventObject;
import com.sun.star.uno.AnyConverter;
import com.sun.star.xml.crypto.sax.XElementStackKeeper ; // defines a start and a stop routine

// This prevents an error message when executing the script a second time
xClassLoader = java.lang.ClassLoader.getSystemClassLoader();

try {
xClassLoader.loadClass("ms777Timer_01");
} catch (ClassNotFoundException e)
{
System.out.println( "class not found - compiling" );


public class ms777Timer_01 extends PropertySet implements XElementStackKeeper
{

// These are the properties of the PropertySet
public boolean bFixedRate = true;
public boolean bIsRunning = false;
public int lPeriodInMilliSec = 2000;
public int lDelayInMilliSec = 0;
public int lCurrentValue = 0;
public XJobExecutor xJob = null;

// These are some additional properties
Task xTask =null;
Timer xTimer = null;

public ms777Timer_01() {
registerProperty("bFixedRate", (short) 0);
registerProperty("bIsRunning", (short) com.sun.star.beans.PropertyAttribute.READONLY);
registerProperty("lPeriodInMilliSec", (short) 0);
registerProperty("lDelayInMilliSec", (short) 0);
registerProperty("lCurrentValue", (short) 0);
registerProperty("xJob", (short) com.sun.star.beans.PropertyAttribute.MAYBEVOID);
xTimer = new Timer();
}

//XElementStackKeeper
public void start() {
stop();
if (xJob==null) {return;}
xTask = new Task();
lCurrentValue = 1;
bIsRunning = true;
if (bFixedRate) {
xTimer.scheduleAtFixedRate( xTask, (long) lDelayInMilliSec, (long) lPeriodInMilliSec );
} else {
xTimer.schedule( xTask, (long) lDelayInMilliSec, (long) lPeriodInMilliSec );
}
}

public void stop() {
lCurrentValue = 0;
bIsRunning = false;
if (xTask!=null) { xTask.cancel();}
}

public void retrieve(com.sun.star.xml.sax.XDocumentHandler h, boolean b) { }

class Task extends TimerTask {
public void run() { // эта функция вызывается по таймеру и дергает триггер, в который мы передаем либо 0 либо 1
xJob.trigger(lCurrentValue.toString());
if (lCurrentValue == 0)
lCurrentValue = 1;
else
lCurrentValue = 0;
}
}
}

System.out.println( "ms777PropertySet generated" );
} // of if (xClass = null)

Object TA = new ms777Timer_01();
return TA;


LibreOffice Basic часть


Sub clock // эту функцию я повешал на кнопку, чтобы запускать и останавливать "тактовый генератор"
if isEmpty(oP) then // если запустили первый раз, то создаем эти неведомые объекты в которых я не разобрался
oP = GenerateTimerPropertySet()
oJob1 = createUnoListener("JOB1_", "com.sun.star.task.XJobExecutor")
oP.xJob = oJob1
oP.lPeriodInMilliSec = 150 // здесь задается задержка
endif

if state = 0 then // а здесь смена состояния, 0 - означает синхроимпульс остановлен и его надо запустить
oP.start()
state = 1
else // в противном случае означает что синхроимпульс запущен и его надо остановить
oP.stop()
state = 0
endif
End Sub

function GenerateTimerPropertySet() as Any // функция в которой достается срипт на BeanShell
oSP = ThisComponent.getScriptProvider("")
oScript = oSP.getScript("vnd.sun.star.script:timer.timer.bsh?language=BeanShell&location=document")
GenerateTimerPropertySet = oScript.invoke(Array(), Array(), Array()
end function

sub JOB1_trigger(s as String) // это триггер который вызывается по таймеру из BeanShell скрипта
SetCell(1, 2, s)
end sub

sub SetCell (x as Integer, y as Integer, val as Integer) // установить значение в ячейке с координатами X, Y
ThisComponent.sheets.getByIndex(1).getCellByPosition(x, y).Value = val
end sub




Итак, на лист я добавил компонент кнопку, назвал ее «Cтарт/Стоп» и повешал на нее функцию clock. Теперь при нажатии кнопки, ячейка меняла свое значение на 0 или 1 с заданным интервалом, и поток приложения больше не блокировался. Можно было продолжать эксперименты: вешать какие-то формулы на синхро-сигнал и всячески «извращаться».

Тут я начал думать, чего-бы такого сделать. Вот экран есть, логику, вроде как, любую можно реализовать, есть синхроимпульс. А что, если сделать бегущую строку, или, вообще, «Тетрис»? Это ж у меня получается, практически, цифровая схемотехника! Тут вспомнилась занятная игра по цифровой схемотехнике: kohctpyktop, там одно из заданий было сделать сумматор и память с адресным доступом. Если там это возможно было сделать, значит и тут можно — подумал я. А раз есть экран, значит надо сделать игру. А там где одна игра, там и другая, значит надо сделать возможность делать разные игры… Примерно, как-то так, в мою голову пришла идея сделать процессор, чтобы можно было в ячейки заносить команды, а он бы их считывал, менял свое состояние и выводил на экран то, что мне нужно.


Размышлений было много, проб и ошибок тоже, были мысли сделать эмулятор готового процессора, например Z80 и другие не менее безумные мысли… В конце концов я решил попробовать сделать память, стек, регистры и парочку команд типа mov, jmp, математические же команды типа add, mul, sub и т.д. было решено не делать, ибо формулы Calc уже и так это умеют и даже больше, так что я решил использовать в своем «ассемблере» напрямую формулы табличного процессора.




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

m_address - адрес
m_value_in - значение для записи
m_set - сигнал "записать"
m_value_out - значение при чтении, выходной сигнал
m_clear - сигнал на очистку



Чтобы было удобнее, самое время воспользоваться возможностью именовать ячейки в Calc. Становимся на ячейку, Insert/Names/Define… Это позволит дать понятные имена ячейкам и использовать в формулах уже эти имена. Итак, я дал имена 5ти ячейкам, что описаны выше. Дальше выделил квадратную область 10х10 — это те ячейки которые будут хранить значения. По краям пронумеровал строки и столбцы — чтобы использовать номера столбцов и строк в формулах. Теперь каждая ячейка, хранящая значение, заполняется одинаковой формулой:

=IF( m_clear = 1; 0; IF(AND(m_address = ([ячейка_с_номером_ряда] * 10) + [ячека_с_номером_колонки]; m_set = 1); m_value; [текущая_ячейка])),

логика тут простая: сначала проверяется сигнал очистки, если он выставлен, то обнуляем ячейку, в противном случае смотрим совпадает ли адрес (ячейки адресуются числом 0..99, столбцы и строки пронумерованы от 0 до 9) и выставлен ли сигнал на запись, если да, то берем значение на запись, если нет, то сохраняем свое текущее значение. Протягиваем формулу по всем ячейкам памяти, и теперь мы можем заносить в память любые значения. В ячейку m_value_out заносим следующую формулу: =INDIRECT(ADDRESS(ROW([первая_ячейка_памяти]) + m_address / 10; COLUMN([первая_ячейка_памяти]) + MOD(m_address; 10); 1;0);0), функция INDIRECT возвращает значение по ссылке заданной в строке, а функция ADDRESS как раз возвращает строку со ссылкой, аргументы это ряд и колонка листа, и тип ссылки. Я оформил это таким образом:

Тут желтым цветом обозначены входные сигналы, в которые можно писать значения, в них формул нет, а красным выделено то, что трогать нельзя, зеленое поле — это выходное значение, оно содержит формулу и на него можно ссылаться в других формулах.

Память готова, теперь я вздумал реализовать стек. Стек — это такой черный ящик, которому на вход можно подать значение, сигнал на запись и сигнал на чтение. Если подан сигнал на запись, то стек сохраняет значение у себя внутри, рядом с ранее сохраненными, если подан сигнал на чтение, то стек на выходе выдает крайнее сохраненное у себя значение, и удаляет его у себя внутри так, что крайним значением становится предыдущее сохраненное. Здесь уже пришлось повозиться, потому что, в отличие от памяти, стек имеет внутреннюю структуру: указатель на вершину стека, который должен правильно менять свое состояние. Итак, для интерфейсной части я завел следующие ячейки:

s_address - адрес откуда начинаются ячейки для хранения, например "Z2"
s_pushvalue - значение, которое надо записать в стек
s_push - сигнал на запись
s_pop - сигнал на извлечение из стека
s_popvalue - выходной сигнал - значение, извлеченное из стека
s_reset - сигнал сброса



Для внутренних структур я завел следующие ячейки:

sp_address - адрес ячейки куда показывает указатель стека
sp_row - ряд sp_address
sp_column - колонка sp_address
sp - указатель стека, число, например 20 означает что 20 значений уже сохранено в стек и следующее будет 21-е
oldsp - старый указатель стека, нужен для корректной работы sp



Ну и осталась длинная строка ячеек, в которых будут храниться значения. Начнем с формулы для извлечения значения s_popvalue =IF(s_pop=1; INDIRECT(sp_address; 0); s_popvalue), тут все просто, если сигнал для извлечения подан, то просто берем значение ячейки по адресу, куда показывает указатель стека, иначе сохраняем старое значение. Формулы для внутренних структур:






















ячейкаформула
sp_address=ADDRESS(sp_row; sp_column; 1;0)
sp_row=ROW(INDIRECT(s_address))
sp_column=COLUMN(INDIRECT(s_address)) + sp
oldsp=IF(AND(s_push = 0; s_pop = 0); sp; oldsp)



Здесь легко заметить, что для формирования адреса, куда показывает стек, мы берем адрес начала стека и прибавляем к нему указатель стека. Старое значение указателя стека обновляется в случае когда оба сигнала: и на запись и на извлечение — нулевые. Пока все просто. Формула для sp же довольно сложна, поэтому я приведу ее с отступами, для лучшего понимания:

Указатель стека sp


=IF(s_reset = 1; // если сигнал сброса, то
0; // сбросить указатель в 0
IF(AND(sp = oldsp; c_clock = 1); // иначе проверяем равен ли стекпойнтер старому значению и взведен ли синхросигнал (то есть надо ли обновить стекпойнтер)
SUM(sp; IF(s_push = 1; // если обновление стекпойнтера требуется, значит к старому значению прибавляем некое смещение (-1, 0 или 1)
1; // прибавляем к стекпойнтеру 1, в случае если сигнал push
IF(s_pop=1; // в противном случае, если сигнал pop, то прибавляем либо 0 либо -1
IF(sp > 0; -1; 0); // -1 прибавляем в случае, когда sp > 0, иначе прибавляем 0, то есть оставляем старое значение
0))); // старое значение оставляем в случае когда ни push ни pop не взведены
sp)) // если стекпойнтер не равен старому значению, или синхросигнал невзведен то сохраняем старое значение




5 вложенных IF выглядят монстрообразно, в дальнейшем я такие длинные формулы разделял на несколько ячеек так, чтобы в каждой ячейке было не больше 2-х IF'ов.

Осталось привести формулу для ячеек, хранящих значение:



=IF (s_reset = 1; 0; IF (AND(s_push = 1; ROW([текущая_ячейка]) = sp_row; SUM(COLUMN([текущая_ячейка]); 1) = sp_column; oldsp <> sp); s_pushvalue; [текущая_ячейка]))

здесь в принципе можно «распарсить» без отступов, суть такова, что проверяется некоторое условие и в случае, когда это условие выполняется — в ячейку заносится s_pushvalue. Условие следующее: должен быть взведен сигнал s_push; ряд ячейки должен совпадать с рядом, куда указывает sp; колонка, куда показывает sp, должна быть на 1 больше, чем колонка нашей ячейки; ну и sp не должен равняться своему старому значению oldsp.

Картинка для наглядности, что у меня получилось:




Ну вот, память есть, стек есть. Экран я сделал побольше чем 8х8, т.к. изначально думал про тетрис, то сделал 10х20, как на BrickGame из 90х. Первые 20 ячеек своей памяти я использовал в качестве видеопамяти, то есть подключил их к 20 строкам экрана (поэтому на картинке они темно-красного цвета), теперь я могу рисовать на экране что-то, путем занесения в память по нужному адресу нужных мне значений. Осталось реализовать главное: то, что будет пользоваться памятью, стеком, считывать команды и исполнять их.

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


Структуры CPU


Входы:
c_reset - сигнал сброса (обнуляет состояние процессора)
c_main - адрес начала программы, точка входа
c_clock - синхроимпульс, подается извне
pop_value - значение из стека, подключается к стеку =s_popvalue

Внутренние структуры:
command - команда на выполнение
opA - первый операнд команды
opB - второй операнд команды
cur_col - текущий ряд (куда показывает ip)
cur_row - текущая колонка
ip - instruction pointer, указатель на команду
oldip - старый ip, нужен для корректной работы ip
ax - регистр общего назначения (РОН)
bx - РОН
cx - РОН
rax - копия ax, нужна для того, чтобы корректно модифицировать значение ax
rbx - копия bx
rcx - копия cx

Выходы:
mem_addr - адрес памяти, подключено к памяти
mem_value - значение для записи в память или считанное из памяти
mem_set - сигнал для записи в память, подключен к памяти

pop_value - значение из стека, или для записи в стек, подключено к стеку
push_c - сигнал записи в стек
pop_c - сигнал чтения из стека






Вкратце, как все работает: входы подключены к тактовому генератору и сбросу (который я повесил на кнопку для удобства, чистая формальность), точка входа настраивается вручную. Выходы подключены к памяти и стеку, на них, в зависимости от команд, будут появляться нужные сигналы. Команда и операнды заполняются, в зависимости от того, куда показывает указатель инструкций ip. Регистры меняют свое значение, в зависимости от команд, и операндов. ip тоже может менять свое значение, в зависимости от команды, но по-умолчанию он просто увеличивается на 1 на каждом шаге, а начинается все с точки входа, которую указывает человек. Т.о. программа может располагаться в произвольном месте листа, главное — адрес первой ячейки указать в c_main.
Список команд поддерживаемый процессором:


mov - поместить значение в регистр, первый операнд имя регистра, второй - значение, например mov ax 666
movm - поместить значение по адресу в памяти, первый операнд - адрес в памяти, второй операнд значение
jmp - переход, один операнд - новое значение ip, второй операнд отсутствует (но в ячейке все-равно должно что-то быть! Магия Calc, которую я не разгадал...)
push - достать значение из стека и положить в регистр общего назначения, единственный операнд - название регистра (ax, bx или cx), магия со вторым оператором такая же
pop - положить значение в стек, операнд - значение
mmov - достать значение из памяти и положить в регистр, первый операнд - адрес памяти, второй операнд - название регистра






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

Начнем с простых внутренних структур: cur_col=COLUMN(INDIRECT(ip)) и cur_row=ROW(INDIRECT(ip)) это просто текущий ряд и текущая колонка. command=IFERROR(INDIRECT(ADDRESS(ROW(INDIRECT(ip));COLUMN(INDIRECT(ip)); 1;0); 0); null) здесь уже видно различие теории и практики. Во-первых, пришлось вставить проверку на ошибки. Во-вторых, в формуле пришлось отказаться от предыдущих значений cur_col и cur_row — это приводило к каким-то хитрым циклическим зависимостям и не давало корректно работать ip, впрочем речь об ip ниже. В-третьих, здесь я применил специальное значение null (в случае ошибки), для него выделена отдельная ячейка с "-1".

Значения операндов формируются из текущей строки и колонки со смещением:



opA=IFERROR(INDIRECT(ADDRESS(cur_row; cur_col + 1; 1;0); 0); null)
opB=IFERROR(INDIRECT(ADDRESS(cur_row; cur_col + 2; 1;0); 0); null)



Формула для instruction pointer:


ip=IF(c_reset = 1; // проверка на сброс
c_main; // если был сброс, то возвращаемся на мейн
IF(AND(c_clock = 1;ip=oldip); // в противном случае проверяем надо ли обновлять значение (взведен клок и старое значение совпадает с текущим)
IF(command="jmp"; // если значение менять надо, то проверяем является ли ткущая команда переходом
opA; // если текущая команда jmp, тогда берем новое значение из операнда
ADDRESS(ROW(INDIRECT(ip))+1; // если текущая команда не jmp, тогда просто переходим на следующий ряд
COLUMN(INDIRECT(ip))));
ip)) // если значение обновлять не надо, то оставляем старое



Фактически, эта длинная формула у меня разнесена по нескольким ячейкам, но можно и все в одну записать.

opdip=IF(c_clock = 0; ip; oldip)




Формулы для регистров, также проверяют какая команда является текущей, но учитывается уже больше команд, поэтому уровень вложенности IF совсем нечитабельный. Здесь я приведу пример, как я разносил длинные формулы по нескольким ячейкам:

Регистры общего назначения
Адреса ячеек чисто условные, для примера.


















ABCDE
1=IF(c_reset = 1; 0; B1)=IF (c_clock = 1; C1; ax)= IF(c_clock=1; IF (opA = «ax»; D1; IF(opB = «ax»; E1; ax));ax)=IF(AND(opA = «ax»;c_clock=1);IF (command = «pop»; pop_value; IF (command = «mov»; opB; ax)); ax)= IF(AND(opB=«ax»;command = «mmov»); mem_value; ax)



Здесь A1 и является, собственно, регистром ax, а остальные это вспомогательные ячейки.

Копия регистра rax=IF(c_reset= 1; 0; IF(AND(rax<>ax; c_clock=0); ax; rax))

Думаю тут совсем не сложно догадаться что происходит. Остальные регистры bx и cx устроены аналогичным образом.






Осталось дело за малым — выходные сигналы процессора:


























push_value=IFERROR(IF(command=«push»; opA; push_value);null)
push_c=IF(command=«push»; c_clock; 0)
pop_c=IF(AND(command=«pop»; c_clock = 1); 1; 0)
mem_addr=IF(c_reset = 1; 0; IF(OR(command = «movm»; command = «mmov»); opA; mem_addr))
mem_value=IF(c_reset = 1; 0; IF(command = «movm»; opB; IF(command=«mmov»; m_value_out; mem_value)))
mem_set=IF(c_reset = 1; 0; IF(command = «movm»; 1; 0))



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

Картинка моего процессора:



Здесь видно еще debug поля — в них выводятся не значения, а формулы в виде текста.




Итак, компьютер готов, можно приступать к написанию программы. В процессе программирования обнаружилось несколько проблем, некоторые из которых были решены, некоторые все же остались:


  1. Иногда «компьютер» глючит и ведет себя непредсказуемо

  2. Надо чтобы на листе было видно почти все, включая программу, иначе ячейки, которые далеко за пределом видимости не обновляют свое содержимое

  3. «Компьютер» получился медленный, уменьшение задержки между тиками приводит к тому, что дисплей и некоторые формулы не успевают обновляться. Опытным путем я подобрал, более менее, оптимальную задержку для своего ноутбука: 150-200 мс




Так как каждая строчка «программы» выполняется за один «тик», то строчек должно быть как можно меньше, по возможности надо стараться запихать как можно больше в одну формулу. Главной проблемой оказалось, что код для «Тетриса» получается слишком большой и может совсем не поместится на лист, поэтому было решено (после того, как намучался с «Тетрисом») написать «Змейку» и постараться использовать минимальное число строк для этого.

Интерфейс ввода, т.е. кнопки управления, пришлось сделать на макросах: 4 кнопки со стрелками и 4 ячейки в которые помещается 1, если кнопка нажата, которые я назвал key_up, key_down, key_left и key_right. К ним был прикручен триггер key_trigger=IF(key_up; «U»; IF(key_down; «D»; IF(key_left; «L»; IF(key_right; «R»; key_trigger)))), в котором сохраняется последняя нажатая клавиша.


Также я сделал кнопку «Debug», для отладки программы, с помощью нее можно руками управлять тактовым генератором и смотреть как меняются состояния ячеек (она заносит попеременно 1 или 0 в ячейку clock). Это все за что отвечают макросы: тактовый генератор и органы управления. Больше макросов не будет.


Начал разработку «Змейки» с псевдокода:


Псевдокод 'Змейки'
Для «Змейки» нужны следующие сущности: координаты головы; координаты хвоста; массив, где хранятся координаты всех точек змейки; координаты мячика.

HEAD // ядрес ячейки памяти с координатами головы
TAIL // ядрес ячейки памяти с координатами хвоста
BXBY = rand // координаты мячика
HXHY = *HEAD // координаты головы
TXTY = *TAIL // координаты хвоста

loop:
read DIRECTION // считываем направление (клавишу)
HEAD++ // увеличиваем указатель головы на единицу
HXHY += DIRECTION // векторно прибавляем направление к координатам головы
[HEAD] = HXHY // сохраняем в память новые координаты головы
BXBY <> HXHY ? JMP cltail // если координаты головы не совпали с координатами мячика, то прыгаем на "стирание хвоста"
BXBY = rand // генерируем новые координаты мячика
[BY] = OR([BY]; 9-2^BX) // рисуем мячик на экране (первые 20 ячеек памяти отображаются на экране 10х20)
JMP svtail //перепрыгиваем стирание хвоста
cltail:
[TY] = AND([TY]; XOR(FFFF; (9-2^TX))) // стираем хвост с экрана
TAIL++ // увеличиваем указатель хвоста
TXTY = [TAIL] // берем новые координаты хвоста из памяти
svtail:
[HY] = OR([HY]; 9-2^HX) // рисуем голову на экране

JMP loop // переходим на начало цикла



Вот такой несложный алгоритм получился.

Хранить данные я решил в аггрегированном виде в регистрах, например регистр ax хранит BXBYHHTT, то есть фактически 4 двузначных переменных: координаты мячика (BX и BY), номер ячейки с координатами головы (HH), номер ячейки с координатами хвоста (TT). Это усложняет доступ к переменным, но позволяет уменьшить число строк программы.




Далее нужно было этот алгоритм детализировать. Начнем с инициализации:

Инициализация












































CommandOperand 1Operand 2Comment
movax=RANDBETWEEN(0;9) * 1000000 + RANDBETWEEN(0;19)* 10000 + 2120BXBYHHTT
movm21509Head: x — 5, y — 9
movm20409Tail: x — 4; y — 9
movcxRdirection init
movbx5090409HXHYTXTY
movm=MOD(ROUNDDOWN(rax/10000);100)=2^(9-ROUNDDOWN(rax/1000000))draw ball






Дальше начинается основной цикл. Сначала я просто взял свой псевдокод и начал детализировать каждую его строчку с учетом формул Calc и архитектуры своего процессора. Вид у этого всего вышел страшный:

Псевдокод приближенный к рабочему


loop:
cx = IF(OR(AND(rcx="U";key_trigger="D");AND(rcx="D";key_trigger="U");AND(rcx="L";key_trigger="R");AND(rcx="R";key_trigger="L"));rcx;key_trigger)
ax = IF(ROUND(MOD(rax;10000)/100) < 89; ROUND(MOD(rax;10000)/100)+1; 20) * 100 + MOD(rax;100) + ROUND(rax/10000) * 10000
bx = IF(AND(rcx="U";MOD(ROUND(rbx/10000);100)>0);rbx-10000;IF(AND(rcx="D";MOD(ROUND(rbx/10000);100)<19);rbx+10000;IF(AND(rcx="R";ROUND(rbx/1000000)<9);rbx+1000000;IF(AND(rcx="L";ROUND(rbx/1000000)>0);rbx-1000000;"FAIL"))))
push cx
[ROUND(MOD(rax; 10000)/100)] = ROUND(rbx/10000)
jmp IF(ROUND(rax/10000) <> ROUND(rbx/10000); ctail; next)
ax = MOD(rax;10000) + MOD(MOD(ROUND(rax/10000);100)*11 + 3; 20) * 10000 + MOD(ROUND(rax/1000000)*3+2;10)*1000000 // ball generator
cx = [MOD(ROUND(rax/10000);100)] // get [BY]
[MOD(ROUND(rax/10000);100)] = BITOR(rcx; 2^(9-ROUND(rax/1000000))) // draw ball on scr
jmp svtail
ctail:
cx = [MOD(rbx;100)] // cx = [TY]
[MOD(rbx;100)] = BITAND(rcx; BITXOR(HEX2DEC("FFFF"); 2^(9-ROUND(MOD(rbx;10000)/100)))) // clear tail on scr
ax = IF(MOD(rax;100) < 89; rax + 1; ROUND(rax/100)*100 + 20)
cx = [MOD(rax;100)] // cx = [TT]
bx = ROUND(rbx/10000)*10000 + rcx
svtail:
cx = [MOD(ROUND(rbx/10000);100)] // cx = [HY]
[MOD(ROUND(rbx/10000);100)] = BITOR(rcx; 2^(9-ROUND(rbx/1000000))) // draw head on scr
pop cx
jmp loop



Здесь я заменил переменные псевдокода на регистры, в ax решил хранить 4 двузначных числа: BXBYHHTT, в bx HXHYTXTY, то есть координаты головы и хвоста, а в cx — направление, ну и использовать его для промежуточных нужд. Например, когда надо переложить из памяти в память, напрямую этого сделать нельзя, приходится делать через регистр.




Дальнейшим шагом было только заменить присваивания на команды mov, movm и mmov соответственно и перенести код в ячейки на листе.

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


Для упрощения, проверок на то, что мячик появился посреди змеи не делается. Также не делается проверок на проход змеи сквозь себя.


Работает программа очень «слоупочно». Я записал видео в реальном времени и ускоренное в 16 раз. В конце видео я прохожу сквозь себя и врезаюсь в стену (в регистре bx появляестя «FAIL» и змейка больше никуда не ползет).


Ускоренное в 16 раз видео:


Реальное время




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


Видео ускорено в 16 раз:


Проект доступен на гитхабе, для работы требуется LIbreOffice Calc с установленным BeanShell.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


[recovery mode] 4.04 С днём веб-мастера

image

Сегодня 4.04 всемирный праздник веб-мастера!


Немного истории:



Впервые термин «веб-мастер» ввел в обращение «праотец» Интернета Тим Бернерс-Ли в документе «Руководство по стилю гипертекста в онлайне» в 1992 году. В начале 1990-х, когда «общедоступный» Интернет еще только начинал развиваться, функционал первых веб-мастеров был очень разнообразным: он включал в себя обязанности веб-дизайнера, автора и модератора сайта, программиста, системного администратора, контент-менеджера (ответственного за смысловое наполнение сайта), сотрудника технической поддержки сайта.



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


Так же, сегодня празднуется международный день Интернета.


Хотел пожелать всем успеха в ваших проектах, и начинаниях.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Web Bundle — дело RarJPEG живет

На днях состоялся первый релиз набора инструментов для упаковки различных веб-ресурсов в один файл — Web Bundle. Принцип его работы точно такой же, как для ресурсов, включаемых в EXE файлы — произвольные файлы упаковываются в один файл-контейнер, а на клиентской стороне доступ к ним организуется по имени файла с помощью API. Только в данном случае в качестве контейнера используется изображение в формате PNG. Клиентская часть представляет из себя небольшую JS-библиотечку, позволяющую загружать, декодировать и извлекать отдельные файлы из таких ресурсов.



Тут должна быть картинка с троллейбусом из буханки. Круто, но зачем?! Ведь все ресурсы прекрасно загружаются и без упаковки в контейнер, да еще и параллельно. Это вопрос неоднозначный. С одной стороны, загрузка ресурсов одним файлом может быть быстрее, чем даже параллельная загрузка множества мелких файлов, особенно на мобильном подключении с высокими задержками. С другой стороны, давно есть технологии для объединения однотипных ресурсов в один файл, таких как скрипты, таблицы стилей, иконки. Да и широкое распространение HTTP 2.0 в скором времени должно решить эту проблему. Но все же у Web Bundle есть некоторые преимущества.

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


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




В этом изображении находятся три файла


Для удобства предусмотрены методы чтения ресурса как текста, как JSON объекта и как изображения.


Успех этой технологии будет зависеть от того, найдется ли ей реальное применение. Причем оно может оказаться самым разным. Например, может ли она быть использована для скрытой доставки malware на клиента? Наверное может. Но AV вендоры скорее всего отреагируют быстро и добавят соответствующий модуль распаковки и анализа. Так что для пользователей это не будет большой проблемой. А вот для всяких фото- и скриншот-хостингов может стать проблемой, если им придется сканировать все загружаемые пользователями файлы, это увеличит нагрузку на сервера.


UPD:

Как подсказал iSage, эта идея уже была реализована в PNGfy в 2009 году (надо же, Canvas был уже тогда!), правда, только для одного файла.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Универсальный Nixie-модуль на ИН-12


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


В прошлый раз я с подобным чувством сооружал чиптюновый модуль на AY-3-8912. Результатом остался чрезвычайно доволен, но отмечу, что законченным изделием он не является. Как и герой данного материала, блок газоразрядных индикаторов ИН-12.


Часики на ИНках на Хабре фигурировали не раз (например 1, 2), поэтому поста в стиле «Yet another Nixie clock» не будет. Сосредоточусь на том, чтобы кратко и емко изложить идею блока индикации и особенности реализации.




Принцип действия


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


Управление


Чтобы управлять индикаторами серии ИН, нужно коммутировать высокое напряжение (ток — единицы миллиампер). Жизнь сильно облегчает существование в любимой 155-й серии дешифраторов ИД1 — они непосредственно и предназначены для работы с индикаторами ИН. Можно использовать и обычные дешифраторы, но тогда на их выходы придется ставить транзисторные ключи. Вполне вариант, но имеет смысл только при недоступности К155ИД1.




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


Итерация первая, 16 ножек.


Решаем проблему в лоб. 4 индикатора ИН-12. На каждый из них приходится по одному дешифратору. Аноды через ограничивающие резисторы (50 КОм 0.5 Вт) подключены к источнику высокого напряжения. В моем случае — маленький бесхозный анодно-накальный трансформатор с диодным мостом.


Разведена плата, с применением фоторезиста изготовлена, компоненты установлены, можно пользоваться. 16 входов подключаются к GPIO Raspberry PI, пишется простенькая программка, отображающая время.


Только вот не слишком ли жирно, отдавать такое количество ножек на отображение четырех цифр? Я уж не говорю о том, что кому-то захочется показать секунды, и GPIO «малинки» просто не хватит. И вообще, причем тут Raspberry, если для минимальных часов нам понадобятся восьминогая ATtiny и DS1307?


Итерация вторая, 2 ножки.


Так что, разводим еще одну маленькую платку, которая будет «вторым этажом». На ней располагаются еще две микросхемы — восьмиразрядные сдвиговые регистры 155ИР8. Они у нас будут преобразователем последовательного интерфейса в параллельный.





Сдвиговый регистр работает следующим образом: по фронту импульса на тактовом входе (CLK) происходит запись в регистр бита на информационном входе (A) с одновременным сдвигом уже имеющихся битов. Восемь выходов регистра отражают его содержимое — они отправляются на входы двух дешифраторов. Два регистра объединяются в один 16-разрядный путем соединения последнего выхода одного со входом другого. Так что, от устройства, управляющего модулем индикации, требуется выдавать 16 бит (по 4 бита на разряд индикации), не забывая каждый зафиксировать тактовым импульсом.


И что?


И все. Есть базовая часть модуля индикации, которую можно использовать, если вам не жалко 16 пинов. Есть дополнительная плата, которая сокращает необходимое число управляющих пинов до двух. Питание — 5 вольт и что-то в районе 180-220 (по 2 мА на индикатор). Можно использовать трансформатор, можно — импульсный преобразователь. Можно — от сетевого напряжения через диод, как это рекомендуется в древних мануалах. Последнего варианта я убоялся, т.к. не люблю, когда у меня по плате свободно гуляет никак не развязанная «сеть».


Ближе к готовому устройству


Все наработки публикуются на Github. В настоящий момент там можно лицезреть готовые разводки плат (Sprint Layout 6), схемы (Eagle) и программу (Python) для Raspberry Pi. Последняя находится в процессе пиления. На момент публикации реализован вывод времени, эффект рандомной прокрутки цифр (надо периодически зажигать все катоды ИНки во избежание т.н. «отравления»), начата работа над будильником. Ведь я не просто так делал модуль на AY-3-8912, он здесь тоже поучаствует. Питоний код в рамках данного материала я описывать не буду, это для третьей части, посвященной сборке в один мегадевайс Raspberry Pi, Nixie-индикатора и чиптюнового модуля.


Гитхаб (основной проект)

Гитхаб (чиптюн)

Индикатор ИН-12

Дешифратор ИД1

Сдвиговый регистр ИР8


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Шифрование в NQ Vault оказалось обычным XOR-ом, и это не самое плохое

NQ Vault — довольно популярное (30 млн. пользователей) Android приложение (есть версия и для iOS), позволяющее зашифровать выбранные SMS, фотографии и видео на устройстве. Просмотреть зашифрованный контент можно через приложение, введя пароль. Приложение получило хорошие отзывы и обзоры в ведущих ИТ изданиях.

Пользователь GitHub ninjadoge24 решил проверить, насколько хорошо приложение защищает приватные данные.



Исследователь начал с того, что создал простой PNG файл размером 1х1 пиксель и добавил в конец файла сигнатуру «NINJADOGE24». Далее он загрузил файл на устройство и зашифровал с помощью NQ Vault с простым паролем. В базе данных приложения (в формате SQLite) было найдено расположение зашифрованного файла. Сравнение зашифрованных данных с исходными показало первый fail.


Исходный файл



0000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR
0000010: 0000 0001 0000 0001 0802 0000 0090 7753 ..............wS
0000020: de00 0000 0970 4859 7300 0003 b100 0003 .....pHYs.......
0000030: b101 f583 ed49 0000 0007 7449 4d45 07df .....I....tIME..
0000040: 0401 0319 3a3d ca0b 0c00 0000 0c69 5458 ....:=.......iTX
0000050: 7443 6f6d 6d65 6e74 0000 0000 00bc aeb2 tComment........
0000060: 9900 0000 0f49 4441 5408 1d01 0400 fbff .....IDAT.......
0000070: 00ff 0000 0301 0100 c706 926f 0000 0000 ...........o....
0000080: 4945 4e44 ae42 6082 4e49 4e4a 4144 4f47 IEND.B`.NINJADOG
0000090: 4532 340a E24.




Зашифрованный файл

0000000: 8d54 4a43 090e 1e0e 0404 0409 4d4c 4056 .TJC........ML@V
0000010: 0404 0405 0404 0405 0c06 0404 0494 7357 ..............sW
0000020: da04 0404 0d74 4c5d 7704 0407 b504 0407 .....tL]w.......
0000030: b505 f187 e94d 0404 0403 704d 4941 03db .....M....pMIA..
0000040: 0005 071d 3e39 ce0f 0804 0404 086d 505c ....>9.......mP\
0000050: 7047 6b69 6961 6a70 0404 0404 04b8 aab6 pGkiiajp........
0000060: 9d04 0404 0b4d 4045 500c 1905 0004 fffb .....M@EP.......
0000070: 04fb 0404 0705 0504 c302 966b 0404 0404 ...........k....
0000080: 4945 4e44 ae42 6082 4e49 4e4a 4144 4f47 IEND.B`.NINJADOG
0000090: 4532 340a E24.




Что это? Сигнатура в конце файла осталась нетронутой! А шифрование других участков подозрительно напоминает простую замену. Применив операцию XOR между исходным и шифротекстом, исследователь получил ключ: 0x04. Да, именно так, XOR однобайтовым ключом, то есть простая замена. Напоминает детство, «школьные» шифры со сдвигом на несколько букв по алфавиту, не так ли? И это в приложении, имеющем платную версию за $7.99 в год!

Оставалось выяснить, почему сигнатура в конце файла осталась неизменной. После написания на скорую руку скрипта для шифровки/дешифровки и еще одного эксперимента с JPEG файлом, открылась еще одна печальная истина: шифруются только первые 128 байт файла, остальное остается нетронутым. А зачем, если первых 128 байт достаточно? Заголовок испорчен, файл не откроется стандартными приложениями, чего еще неискушенному пользователю нужно. Да и по скорости шифрования видео NQ Vault наверное рвет всех конкурентов на тряпки.


И последний штрих. Как из пароля, введенного пользователем, возможно довольно сложного, получается однобайтовый ключ? Исследователь попытался нащупать алгоритм, перебирая различные пароли, но потом плюнул, сделав разумный вывод: зачем тратить на это время, если можно применить брутфорс и просто перебрать 256 возможных вариантов. Любое, даже самое древнее Android устройство отлично с этим справится.


Полный отчет о тестировании.


Заключение




Таких приложений, наверное, тысячи, как в Google Play, так и в AppStore. Просто его сумели удачно раскрутить, а другие нет. Но вот что примечательно в этой истории. Во-первых, насколько легко пользователи доверяют обещаниям безопасности. В сознании пользователя нет большой разницы между приложением для быстрого выкладывания фоток куда-нибудь и приложением, шифрующим файлы или хранящим данные кредитных карт. Нет понимания, что к приложениям связанным с безопасностью, нужно применять особые критерии выбора. Просто обещаний, хорошего внешнего вида, удобного интерфейса и даже тысяч положительных отзывов тут недостаточно.

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


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


[Из песочницы] WPF: использование Attached Property и Behavior

Пожалуй, любой разработчик WPF знает о механизме Attached Property, но многие даже не слышали о Behavior. Хотя эти механизмы и имеют схожие функциональные возможности, они, все же, имеют совершенно разную смысловую нагрузку, и очень важно правильно различать их и использовать.

Давайте вспомним, что из себя представляют эти механизмы:


Attached Property. Это Dependency Property, которое объявлено не в классе объекта, для которого оно будет использоваться, но ведет себя, как будто является его частью.


Объявляется в отдельном классе, имеет getter и setter в виде статических методов. Можно добавить обработчик на PropertyChanged событие.



public static class UiConfigurator
{
public static readonly DependencyProperty CustomValueProperty = DependencyProperty.RegisterAttached(
"CustomValue", typeof(bool), typeof(UiConfigurator), new PropertyMetadata(false));

public static void SetCustomValue(DependencyObject element, bool value)
{
element.SetValue(CustomValueProperty, value);
}

public static bool GetCustomValue(DependencyObject element)
{
return (bool)element.GetValue(CustomValueProperty);
}
}




Используется так же как и обычное Dependency Property, только необходимо указать класс, в котором оно определено.

<Button testApp:UiConfigurator.CustomValue="True"/>




Благодаря обработчику PropertyChanged очень часто с помощью этого механизма пытаются добавлять к UI элементу некоторую функциональность. Например, мы хотим запоминать расположение и размер окна между запусками приложения. Делаем Attached Property SaveBounds и добавляем обработчик PropertyChanged. Если установленно в true, то выполняем код по восстановлению/сохранению позиции окна. Правильно ли это? Нет. Давайте посмотрим как Microsoft использует этот механизм у себя. Отличными примерами являются Grid.Column и DockPanel.Dock. Все эти свойства никак не влияют на функциональность объекта, а просто добавляют некоторую информацию, чтобы другие участники дерева отображения смогли более корректно с ним взаимодействовать. Сам же объект об этом ничего не знает и знать не должен. Отсюда следует, что само по себе использование события PropertyChanged для Attached Property уже повод задуматься: а все ли я правильно делаю? Он необходим только в очень редких случаях. Например, так мы может запоминать историю изменения этого свойства, чтобы при необходимости провести более глубокий анализ и взаимодействовать более интеллектуально. Но это из разряда задач “делал один раз в жизни”.

Так как Attached Property не влияет на состояние объекта, то и несколько таких свойств конфликтовать не должны, а значит мы можем навешивать их на него сколько угодно. Например, те же Grid.Column и DockPanel.Dock (хоть это и несколько нелогично) можно легко сочетать. Вот вам и вторая подсказка правильно ли вы используете этот механизм: если вы можете представить код, который может быть написан в целевом объекте или где-либо еще и который будет конфликтовать с вашим свойством – вы что-то сделали не так.


Behavior. В WPF есть абстрактный шаблонный класс Behavior. Сделав наследника, мы можем присоединить его к объекту в дереве отображения. В этом классе есть методы OnAttached и OnDetaching, которые вызовутся в соответствующих случаях.

Ниже пример CloseBehavior, который можно присоединить любой кнопке и сделать ее таким образом кнопкой закрытия приложения.



public class CloseBehavior : Behavior<Button>
{
protected override void OnAttached()
{
AssociatedObject.Click += OnClick;
}

protected override void OnDetaching()
{
AssociatedObject.Click -= OnClick;
}

private void OnClick(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
}




Добавляется следующим кодом:

<Button Content="Close">
<i:Interaction.Behaviors>
<testApp:CloseBehavior/>
</i:Interaction.Behaviors>
</Button>




Вот Behavior уже в отличии от Attached Property служит для добавления функциональных возможностей UI элементам. В него можно добавлять любые Dependency Property, и, так как он является частью дерева отображения, Binding тут отлично работает. Так как для WPF принято использовать MVVM, а он, в свою очередь, предполагает минимум кода в теле View, то Behavior тут как нельзя кстати. Эти немногочисленные функциональные блоки, которые нельзя выполнить при помощи стандартного Binding, можно выносить и использовать повсеместно комбинирую друг с другом.

Не все объекты Behavior должны быть совместимы друг с другом. Например, мы не сможем совместить CloseButtonBehavior и HelpButtonBehavior. И это, разумеется, нормально.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


пятница, 3 апреля 2015 г.

[Перевод] Лучшая иконка — текст

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

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



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



В какое-то время иконки стали популярны в пользовательских интерфейсах. Взгляните-ка на снимок первого коммерческого компьютера с визуальным управлением (the Xerox Star). Дизайнер Дэйвид Смит изобрал концепцию пиктограмм:



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


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


Это напоминает мне картинку из Twitter'а:



Думаю, Рон запутался, пока пытался осознать суть всех этих значков для стирки белья…


Когда я обсуждаю использование иконок с клиентами, то всегда слышу аргумент вроде «Люди будут использовать наш продукт ежедневно и запросто выучат предназначение всех кнопок». Вполне справедливо, но есть тут и изъян. Я использую почтовое приложение от Apple несколько раз в день, но до сих пор не всегда могу с первого раза вспомнить, какая из иконок служит для отправки писем:



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



Другой пример: после редизайна пользователи нового Twittera не могли сразу понять, что им нужно делать. Иконка не была настолько понятна:



Более поздняя версия добавила больше ясности:



Вы знали, что можете отправить фото своему другу в Instagram напрямую? Да, но это все спрятано вот за такой пиктограммой:



Если бы вы были пользователем этой сети ранее, то шансов того, что вам ее доводилось видеть, больше. Но что она означает?


Конечно, необходимо использовать иконки в верном контексте. Некоторые кнопки могут быть интерпретированы двояко. Давайте посмотрим на еще один пример. При открытии переписки в Gmail любо пользователь видит вот это. Окей, как теперь вернуться назад?



Со мной не раз случалось, что я возвращался во «Входящие» вместо того, чтобы написать ответ.


Иконки в Tweetbot могут быть понятны не всем, но они взяты из контекста Twitter'a. Пользователи Tweetbot зачастую являются пользователями Twitter, так что использование данных кнопок вполне приемлемо:



То же можно сказать и о приложении Tumblr для iOS. Некоторое для нас непонятно, но в контекста Tumblr многое проясняется. Пользователи данного сервиса явно все поймут.



Приложение Mac Rdio выглядит так:



Многие пользователи смогут разобраться, так как перед ними аудиоплеер. (Однако, одну из иконок можно принять как за уровень громкости, так и за текущую проигрываемую дорожку.)


Дайте мне сказать это еще раз: не используйте вы эти иконки, если не все их понимают. Если ситуация спорная, то лучше напишите все текстом, так как это будет точно яснее.


Но никто не запрещает вам использовать надписи вместе с картинками. Это является идеальным решением.


Mac App Store именно так и делает:



Twitter использует этот же прием в своем веб-интерфейсе:



Давайте посмотрим на Facebook в качестве последнего примера: недавно они заменили свою иконку-гамбургер на более удобную навигацию. Хорошо сработано:



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


Берегите себя и своих близких.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


IBM PC своими руками — это очень просто

После того, как я воплотил свою давнишнюю мечту и все-таки (хотя и с опозданием почти на 30 лет) построил Радио 86РК, некоторое время мне казалось, что на этой части моей истории поставлена вполне достойная точка.

Тем не менее, обнаружилось, что болезнь до конца не вылечена, и она вернулась еще более острым рецидивом. Наверное, сказались как неожиданно успешный опыт постройки 86РК, так и то, что у меня в ходе данного процесса образовалось довольно большое количество весьма притягательно выглядящих инструментов, приборов и деталей, которым очень хотелось найти применение.

В конце концов ломка стала нестерпимой, и мне пришлось снова взяться за паяльник, а также вспомнить некоторые другие навыки из прошлого. Что из этого получилось, можно увидеть вместе с некоторым количеством картинок и очень (повторяю – ОЧЕНЬ) большим количеством букв (и даже не букв, а страниц) дальше…



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


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


Схему для себя я даже рисовать не стал – вроде все настолько просто, что вопросов, куда что подключать, не возникало. Оставалось только решить, каким способом это все собирать. С МГТФ я достаточно поигрался раньше, хотелось чего-то новенького. Так как все новое – это забытое старое, то я прикупил инструмент и материалы для монтажа накруткой и взялся за процесс. Который (процесс), к сожалению, особо никуда не пошел… Либо руки у меня совсем из неправильного места растут, либо что-то понял неправильно, но добиться устойчивой скрутки так и не получилось.


Впрочем, мучился я недолго (хотя, может именно в этом и была основная причина ?) – в процессе перекладывания моих сокровищ с места на место обнаружилась нераспечатанная беспаечная макетная плата с кучей очень красивых разноцветных проводков. Сложность (вернее, отсутствие таковой) проекта вполне позволяла без труда разместить все на этой плате, что я довольно оперативно и сделал:


image


Была написана суперпрограмма размером в десяток байт, реализующая не имеющую мировых аналогов функцию – мигание светодиодом. При первом включении ничего не заработало, но обнаружилось, что проблема только в запуске тактового генератора. Мне было лень ставить указанные в документации конденсаторы/резисторы возле кварца, поэтому иногда для запуска генератора требовалось коснуться корпуса кварца пальцем или отверткой, после чего светодиод начинал исправно мигать.


Кстати, меня немного удивила частота мигания – я уже забыл, насколько этот процессор был медленный. При максимально допустимой для оригинального 8088 тактовой частоте в 5МГц цикл в 65536 «пустых» операций выполнялся порядка секунды…

Опубликовав результат своего творчества на соответствующем форуме, я тут же был обвинен в том, что обсуждалось подключение 8086, а у меня же 8088, что намного проще и вообще!


Ладно, практика – критерий истины. Раздобыл 8086, потратил еще чуть-чуть времени (в основном, чтобы разнести вручную программу в две микросхемы ПЗУ, так как 8086 16-разрядный, а ПЗУ у меня были 8-разрядные) и получил очередную мигалку:


image


Апетит, как известно, приходит во время еды. Вот и у меня, вместо удовлетворения от достигнутого, возникло желание двинуться дальше. Только тут уже совсем настойчиво утвердилась мысль, что нужно также опробовать и современные технологии. В качестве примера современных технологий было принято решение использовать FPGA в комбинации с 8088 процессором. Одним махом можно было убить нескольких зайцев – база (процессор) знакомая, технологии FPGA вполне современные, особо тратить время на монтаж не нужно, так как все творчество можно перенести внутрь FPGA.


У меня уже имелась одна из самых навороченных отладочных FPGA плат – Terasic DE2-115, на которой, кроме довольно большой FPGA Altera Cyclone IV, также установлено немереное количество прибамбасов, свисталок и мигалок:


image


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


Правда, есть нюанс – процессор 8088 5-вольтовый, а вот FPGA уже давно отказались от поддержки TTL 5V, максимум, что есть – LVTTL 3.3V


Хорошо, что есть широкий выбор преобразователей уровней, что и решено было использовать. Вначале остановился на микросхеме TXB0108 – 8-разрядный двунаправленный преобразователь уровней с автоматическим выбором направления. Автоматический выбор направления был довольно важен, так как позволял не думать о том, что происходит на шине данных – чтение или запись. Кроме того, шина данных в 8088 мультиплексирована с 8 младшими разрядами адресной шины, что еще добавляет сложности в определении того, в каком направлении нужно передавать сигналы – от процессора или к нему.


Так как TXB0108 довольно маленькая, да и возиться с проводами уже не очень хотелось, решил попробовать еще одну новую для меня вещь. Была куплена программа Eagle для разводки печатных плат, и начались мои мучения как с освоением нового программного продукта вообще, так и с весьма специфическим интерфейсом Eagle в частности.


Делать что-то в первый раз в новой для себя области лично для меня очень мучительно – тыкаешься, как слепой котенок, а душа рвется на просторы. Подготовка gerber-файлов для производства платы (да, о ЛУТе думал, и даже материал купил, но не решился) вообще чуть не вызвала нервный срыв – сразу представлялось, как все производство стоит и хохочет, глядя на жалкие результаты моих трудов…


Хорошо, что плата была совсем уж простая, поэтому не прошло и недели (вместе со временем изготовления), как я уже держал в руках свежеспаянное творение:


image


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


За время, пока плата изготавливалась, я ускоренными темпами осваивал FPGA. Вообще отладочную плату я изначально купил, чтобы поразвлекаться немного с FPGA технологиями и, в частности, с VHDL, но это у меня конкретно не пошло. Мозг просто отказывался мыслить категориями VHDL, и максимум, что я сделал – повторил в FPGA несколько простых устройств, скопировав их схемы методом схемного дизайна. Изучение же VHDL закончилось на уровне signal3 <= signal1 and signal2; дальше чтение учебников оказывало на меня отличный усыпляющий эффект.


Тут тоже решил все делать с помощью схемного дизайна. И вообще – нафига все эти HDL’ы, когда так просто мышкой нарисовать схему, и все работает? В тот момент мне это казалось и правильнее, и удобнее. Соответственно, нарисовал в Quartus’е все ту же мигалку, только пришлось еще сделать аналог тактового генератора 8284 (на макетке он у меня был в «железном» виде). К счастью, в документации нашлась полная внутренняя структура этой микросхемы, так что с этим проблем не возникло. Хотя один нюанс обнаружился – в документации от Intersil (производитель устаревших микросхем) внутренняя структура 8284 была нарисована с ошибкой (инверсный выход вместо прямого, или наоборот – уже не помню). Правда, ошибку отловил еще при рассмотрении схемы, но сам факт очередной раз натолкнул на мысль, что нужно руководствоваться оригинальными документами.


Далее было сгенерировано ПЗУ с соответствующей программой, моя плата с процессором подключена плоским шлейфом к отладочной плате, и наступил момент истины – загрузка прошивки в FPGA. К сожалению, светодиод после этого не замигал… После короткого разбирательства выяснилось, что я подал 0 на один из входов процессора, тогда как там должна быть 1. Данное недоразумение легко исправилось перерезанием дорожки и пайкой перемычки. После этого светодиод мигнул, но так и застыл в горящем состоянии…


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


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


Кроме того, также решил расширить задачу и сделать все по максимуму, в буквальном смысле – запустить процессор в максимальном режиме. В данном режиме процессор не является единоличным владельцем системной шины, и может отдавать ее другим устройствам (типа DMA и т.д.). Именно в таком режиме 8088 работает, в т.ч., и в IBM PC совместимых компьютерах. Для работы в максимальном режиме необходимы определенные сигналы, которые обычно формируются с помощью контроллера шины 8288. Теоретически, эти сигналы можно было сформировать внутри FPGA, но у меня не возникло полной ясности после довольно внимательного чтения документации по 8288, поэтому было принято решение использовать «железный» 8288, наряду с таким же 8284 (гулять так гулять !). В результате получалось гарантированно работающее (как мне казалось) ядро, вокруг которого уже можно было строить все, что угодно.


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


Правда, из-за того, что полярность сигнала направления была инверсна к тому, что требовалось для преобразователя, пришлось задействовать еще инвертор из 7400. Зато на схеме появилась знаменитая 7400, она же ЛА3, без которой в свое время не обходилось ни одно цифровое устройство.


Очередной раз, пока плата находилась в изготовлении, я рисовал в Quartus’е схему своего суперкомпьютера. В отличие от предыдущего варианта, тут я уже решил добавить нормальную дешифрацию адресного пространства, разделить память и ввод/вывод, а также задействовать ОЗУ. Мало того, вместо мигающего светодиода я сразу замахнулся на целый 7-сегментый индикатор, который должен был увеличивать свое значение по кругу!


Как раз к моменту завершения рисования схемы платы были доставлены, распайка заняла совсем немного времени, и все было готово к загрузке прошивки:


image


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


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


Так вот, на том форуме мне открыли глаза на некоторые вещи, которые для многих покажутся прописными истинами, но для меня оказались неожиданностью. Например, мне показалась странной форма тактового сигнала, приходящего на процессор с чудовищным по амплитуде «звоном»:


image


Оказалось, что этот самый сигнал выглядит совершенно по другому, если всего-навсего «землю» брать не там, где брал ее я (на другом конце отладочной платы, как удобнее), а непосредственно у приемника сигнала (процессора):


image


Вроде мелочь, но, на мой взгляд, именно из таких мелочей и формируется истинное понимание многих процессов…


Кстати, по поводу оборудования, на котором сделана эта картинка. В ходе возни с 86РК у меня появился совсем неплохой портативный осциллограф Fluke, но почему-то к нему у меня сразу душа не легла. Почему – не знаю, не то, и все… Так что новый проект оказался хорошим поводом к приобретению нового осциллографа. Загоревшись идеей, иногда я принимаю поспешные решения. Так и случилось в этот раз. Вместо детального изучения предмета я заимел первое понравившееся устройство – Tektronix MSO3012. Нет, ничего плохого об аппарате я сказать не могу, наоборот – реально удобный интерфейс, куча полезных функций, практически полноценный 16-канальный анализатор цифровых сигналов, возможность просматривать сигналы в виде логической шины, подключение к компьютеру как напрямую, так и через сеть и т.д. Просто всегда хочется большего, и можно было бы выбрать более современную серию – MDO, которая ко всему прочему предлагает еще и встроенный генератор произвольных сигналов. А так инструмент очень крутой – мне аж завидно тем, кто использует подобное оборудование для решения реальных задач, а не для примитивных поделок, как в моем случае…


Возвращаясь к перегреву процессора – на форуме меня убедили, что для данной микросхемы такая температура совершенно нормальна (просто энергопотребление большое). Окончательно я успокоился после того, как поменял процессор на изготовленный по более современной технологии, и он после этого оставался совершенно холодным в течение всего процесса.


Получив в свое распоряжение 8088 с (потенциальной) кучей периферии, у меня совсем уж зачесались руки попрограммировать что-то посложнее управления 7-сегментым индикатором. Правда, встал вопрос о нормальной интегрированной среде для программирования на ассемблере реального режима (единственный язык, который я знал). И вот тут меня ждал большой облом. Если для 8080/Z80, а также для всех современных контроллеров есть масса как бесплатных, так и коммерческих IDE со всеми мыслимыми и немыслимыми прибамбасами, то для x86 не нашлось вообще ничего приличного. Были какие-то заброшенные любительские проекты, и все. Причин этому можно придумать несколько, но факт остается фактом. В конечном итоге, остановился на WinAsm (первое, что у меня хоть как-то заработало), который полноценным IDE назвать вряд ли можно (в первую очередь, из-за отсутствия отладчика), но хоть что-то (типа компиляции прямо из редактора) он позволял делать. В качестве отладчика на тот момент решил использовать старый заслуженный Turbo Debugger, запускаемый в DosBox.


Первым делом мне захотелось получить для своего устройства нормальный способ отображения информации, т.е. видеоадаптер. Хотя в куче мест (в т.ч. и на Хабре) можно найти статьи на тему «Как самому написать видеоадаптер на HDL за 5 минут», сделать свой модуль для меня было просто недостижимой мечтой. Поэтому я залез на широко известный opencores.org и нашел там самый простой алфавитно-цифровой VGA видеоадаптер, да еще и на VHDL (бОльшая часть проектов на opencores написана на Verilog).


Хотя данный модуль был практически законченным устройством, тем не менее, для работы с моей отладочной платой требовалась некоторая доработка (связанная, в первую очередь, со спецификой цифро-аналоговой части VGA-интерфейса DE2-115). Вооружившись моими зачаточными знаниями отдельных выражений VHDL, а также (в основном) методами научного тыка и последовательных приближений, в конце-концов удалось сделать что-то, что вроде отвечало моим потребностям, и компилировалось без ошибок.


К этому моменту я уже начал более-менее ориентироваться в схемном дизайне, так что преобразовать далее модуль видеоадаптера в символ и вставить в свою схему труда не составило. Сначала в качестве видеобуфера я использовал сгенерированное внутри FPGA ПЗУ с заранее записанным тестовым сообщением. Довольно быстро я увидел это сообщение на экране VGA монитора, после чего можно было менять буфер на ОЗУ. В этот момент в очередной (и далеко не последний) раз я ощутил всю прелесть FPGA. В видеоадаптерах всегда есть конфликт между необходимостью непрерывно читать видеобуфер для вывода его на экран, и потребностью процессора записывать в этот же буфер данные (а иногда и тоже их читать). Решается задача по разному, но, в любом случае, это далеко не самый простой узел (как минимум, для меня). А вот при наличии FPGA все сделалось элементарно – я просто сгенерировал двухпортовое ОЗУ, у которого было два комплекта шин адреса и данных. Процессор, естественно, подключался к одному порту, видеоадаптер – ко второму. Что там происходило внутри ОЗУ, и как убирались конфликты при одновременном обращении к одной и той же ячейке памяти – это были проблемы Altera, но никак не мои.


В качестве основного ОЗУ использовал имеющуюся на плате статическую RAM объемом 16х1M (в смысле, 1024К 16-битных слов). В качестве ПЗУ все так же использовался ROM, сгенерированный внутри FPGA – хотя на отладочной плате есть flash-память более чем достаточного объема, но для отладочных целей намного удобнее использовать встроенную память, тем более, что недостатка ее я не испытывал.


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


В голове очередной раз стала появляться мысль, которую я уже несколько раз упорно отгонял – «Может, DOS ?..». И в какой то момент, попав на пик самоуверенности, я сдался… Итак, будем пробовать запускать MS-DOS!


Очевидно, для запуска DOS мне нужно будет реализовать необходимые функции BIOS, но что скрывается под словом «необходимые»? В принципе, я знал, где найти максимальный минимум (или минимальный максимум ?) функций – а именно в первой версии BIOS’а для IBM PC. Так как уже на этом BIOS’е должны были работать все версии DOS, то в любом случае можно было бы ограничиться только его функциями. Найти исходники BIOS в интернете труда не составило, а беглый просмотр показал, что ничего особо загадочного там нет. Фактически, нужна была работа с клавиатурой INT 16h, видеоадаптер 10h, диск 13h и еще несколько простейших функций типа возврата объема доступной оперативной памяти, которые реализовывались буквально несколькими строчками ассемблера.


Первым делом в глубинах интернета был найден VHDL модуль для работы с PS/2 клавиатурой и внедрен (все тем же схемным дизайном) в мою схему. С контроллером прерываний было решено пока не заморачиваться, так как клавиатура на этот момент планировалась единственным источником прерываний.


Итак, можно было приступать к написанию обработчика INT 09h – прерывания клавиатуры. И тут меня ждала очередная засада. В позапрошлой жизни я довольно серьезно программировал на x86 ассемблере, но это было так давно, что почти все тонкости из головы улетучились вчистую. Нет, ясно, что mov и cmp забыть сложно, но все сложнее этого давалось с огромным трудом. Для меня нет ничего хуже, чем делать то, чего уже когда-то делал, и обучение чему-то не является исключением. Особенно если помнишь, что когда-то был довольно крут в чем-то, а сейчас не можешь сказать ни бе, ни ме… Пришлось, стиснув зубы, скачать какой-то учебник по ассемблеру и в экспресс-режиме его прочитать.


Естественно, вспоминать легче, чем начинать с нуля, но мои программы, особенно вначале, мягко говоря, элегантностью не отличались. Приблизительно как попытка пересказать Шекспира на английском языке, пользуясь словарным запасов в сотню слов и двумя временами…

Тем не менее, довольно быстро минимальный набор INT 09/16 заработал, а за ним и была сделана поддержка нескольких основных функций INT 10h для вывода символов на экран. Можно было приступать к намного более сложной вещи – работе с диском.


Естественно, поддерживать реальный жесткий диск я не собирался. Идея была в том, чтобы эмулировать жесткий диск через работу с SD-картой, тем более, что на отладочной плате был разъем для такой карты. С образом диска проблем не возникло – чтобы не ходить далеко, я взял образ диска для уже упоминавшегося здесь проекта zet.aluzina.org


С поддержкой же работы SD-карты возникло сразу два больших вопроса – аппаратная поддержка шины SPI и протокол взаимодействия с самой картой.


В принципе, SPI можно реализовать полностью программно, но мне хотелось поразвлекаться и с «железом» тоже, поэтому я героически принялся за рисование приемо-передатчика байта в схемном дизайне. К моему удивлению, ничего сложного в этом не оказалось, и довольно скоро я уже наблюдал на экране осциллографа резво бегающие 8-битовые пакеты, содержащие именно то, что мне хотелось. Кстати, тут я впервые оценил возможность нового осциллографа не просто показывать кучу сигналов, а еще и объединять их логически в соответствующую шину. Намного приятнее видеть, что осциллограф понял, что передается именно байт A5, а не вручную смотреть, в нужных ли местах находятся переходы с 0 в 1 и наоборот.


С протоколом общения с SD-картой было слегка сложнее, но не намного. В интернете есть куча ресурсов, где все тщательно разжевывается, поэтому поиск необходимой информации не составил большого труда. Для упрощения задачи я не пытался подстраиваться под все типы и разновидности карт, а ограничился оригинальной SD (не SDHC или еще какие-то варианты) картой. Немного программирования, и вот уже на экране стало отображаться содержимое 0-го сектора карты. Сразу после этого привел эти функции в некоторое подобие INT 13h, добавил в зачаточном виде INT 19h (boot load) и увидел на экране следующее:


image


Так как в тот момент при чтении всегда считывался только 0-ой сектор, то начальный загрузчик (находящийся как раз в этом секторе), не находил ОС для загрузки, о чем и сообщал. Но это уже мелочи – главное, что моя схема потихоньку начала превращаться в настоящий компьютер и уже даже пыталась загрузиться!


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


Естественно, сразу загрузка не прошла, да я и не ожидал этого. Заранее зная, что у меня в BIOS’е не реализована куча функций, я поставил на все прерывания заглушки, и при обращении к нереализованной функции на экран выводилась вся необходимая информация – к какому прерыванию и с какими аргументами обращаются. Далее шел процесс написания обработчика соответсвующей функции (а еще чаще – просто временной заглушки), и процесс продолжался. Неожиданно все остановилось на функции, которая вообще отсутствует в оригинальной PC – одна из функций INT 2F, связанную с обработкой событий. Я видел, что DOS определяет тип PC, и вроде не должна вызывать прерывания, отсутствующие на данном типе, но, тем не менее, это происходило, и процесс останавливался. Простая заглушка не помогла, а всю функцию реализовывать не хотелось из принципа.


Сейчас уже не помню весь ход мыслей (очень много чего смотрел в тот момент в исходниках DOS и в процессе загрузки), но в очередной раз на данном «зависании» я решил вызвать кучу прерываний (в тот момент у меня был отключен таймер на INT 08h) и нажал клавишу Shift. Неожиданно случилось чудо:


image


Скажу честно, эмоций на меня нахлынуло довольно много – проделать путь от макетки с парой микросхем до загрузки DOS за месяц, да еще и короткими набегами (из-за хронической нехватки времени) вроде довольно круто (извините за хвастовство)!


Кстати, с этим сообщением у меня есть до сих пор неразгаданная загадка. Дело в том, что после доделки прерывания таймера DOS стала загружаться без зависания в данном месте, но вот сообщение о копирайте Microsoft почему-то не выводится. Вроде оно также не выводится и на настоящем компьютере (к сожалению, попробовать не на чем). В чем тут первопричина – тайна, покрытая мраком. Я пытался понять логику по исходным кодам DOS, но сходу не увидел, а много времени тратить не захотел. Тем не менее, вопрос все еще мучает потихоньку…


После запуска DOS пришла очередь позапускать другие программы. Наверное, можно догадаться, чья очередь была первой – естественно, как говорят, старый добрый Norton Commander. Как ни странно, возни с ним было заметно больше, чем с DOS’ом. NC при запуске вызывал дикое количество функций, причем в ряде случаев обойтись простыми заглушками не удавалось, приходилось писать хотя бы минимум функциональности.


Тем не менее, проблемы были больше количественные, чем качественные, и вскоре удалось довести процесс загрузки NC до логического завершения:


image


Такой «интересный» внешний вид обусловлен несколькими причинами:

— видеоадаптер не поддерживал на тот момент атрибуты

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

— не были реализованы некоторые функции INT 10h.


Вообще меня периодически удивляло, каким именно образом реализованы те или иные функции в различных программах (и даже в DOS). Например, команда CLS (очистка экрана) вызывала функцию INT 10h, вызывающую сдвиг окна вверх. При этом в качестве окна указывалась вся доступная экранная область, и сдвигалась она на количество строк, равное количеству строк на экране. Так как я не ожидал, что функции работы с окнами вообще кто-то использует, то и не спешил их реализовывать. Результат оказался налицо (вернее, на экране). Впрочем, к странностям некоторых программ еще вернемся немного дальше…


После запуска NC у меня возникло естественное желание привести его в божеский вид. Тем более, что такая часть работы иногда даже более приятна, чем попытки завести вообще мертвое устройство. С псевдографикой особых проблем не было – просто довольно много времени на ручное рисование символов (знакогенератор у меня был прямо в виде VHDL кода). А вот с атрибутами пришлось немного напрячься.


Еще раньше, по ходу процесса, я стал применять некоторые элементы VHDL. Сначала практически насильно – все-таки было желание еще раз попробовать освоить этот язык, а потом и потому, что в определенных случаях это оказывалось удобнее, чем использовать схемный дизайн. Даже в самом видеоадаптере мне пришлось вникнуть в код – изначально поддерживалось 43 (или что-то около этого) строки, мне же нужно было переделать на 25 строк. И поддержку атрибутов я сначала попытался сделать схемным дизайном, но вдруг стал осознавать, что вроде использовать VHDL для этого может оказаться проще. Естественно, все двигалось с большим трудом и использованием самых простых конструкций языка, но я вдруг начал понимать суть VHDL – пока еще совсем чуть-чуть, но уже достаточно, чтобы начать на нем что-то осознано создавать, а не просто модифицировать уже имеющееся.


Моя возня с VHDL не прошла даром, и через некоторое время я смог увидеть что-то давно и хорошо знакомое:


image


Да, там еще можно было заметить некоторые недоделки (типа сдвинутого на один символ атрибута), но в целом цветной текстовый режим 80x25 заработал так, как должен.


Следующим на очереди стоял контроллер прерываний 8259. Сначала возникла мысль попытаться использовать уже имеющийся из какого-то проекта, но ни один из них мне по разным причинам не понравился (либо слишком примитивные, либо, наоборот — я не понимал, как они работают, а документация отсутствовала). Была даже попытка купить коммерческую IP (в данном случае IP это не Internet Protocol, а Intellectual Property), но производители не хотели заморачиваться с продажей целой одной штуки…


В конечном итоге пришлось взяться за листик бумаги и набросать нечто типа (блок)схемы контроллера, которую потом начал реализовывать на VHDL. За полной совместимостью не гнался – мне нужна была (на данном этапе) поддержка одного основного режима приоритетных прерываний, возможность маскировать прерывания (также читать маску прерываний) и выполнять команду EOI (End Of Interrupt). На мой взгляд, этого должно быть достаточно, чтобы подавляющее большинство программ с этим нормально работали. Забегая вперед, скажу, что и по настоящий день я не обнаружил ни одной программы, которая пыталась бы сделать с контроллером прерываний что-то свыше заложенной мною функциональности.


Наверное, контроллер прерываний был моим первым настоящим (пускай и маленьким) проектом на VHDL – от начала и до конца. Писал я его тщательно, не поленился даже (опять таки впервые в своей жизни) сделать test bench (не уверен, как правильно перевести на русский – фактически, последовательность сигналов для проверки правильности функционирования устройства). Моделирование в симуляторе ModelSim показало вроде полную работоспособность контроллера, после чего из него был сгенерирован очередной графический символ и добавлен в мое устройство.


Нормального таймера 8254 у меня еще не было, для генерации прерываний 18.2 Гц использовался обычный счетчик, который я и подключил к контроллеру прерываний. Поведение компьютера показало, что вроде все работает – DOS загрузился без необходимости нажимать на клавишу, а в NC наконец-то пошли часы. Казалось, пройден очередной этап, и можно смело двигаться дальше.


Как оказалось, рано я радовался – в этот момент обнаружилась, пожалуй, самая большая проблема во всем проекте. Если кто помнит, у NC есть встроенная экранная заставка – «звездное небо». Оставив мой компьютер на некоторое время, после возвращения к нему я обнаружил, что звезды на заставке почему-то застыли, проще говоря, компьютер завис. Хотя я понимаю, что таких случайностей не бывает, мне все-таки хотелось верить в чудо – в то, что это единичный случай. К сожалению, как всегда, чуда не случилось – после полного сброса и перезагрузки компьютер снова подвис после часа или около того работы. Стало однозначно понятно, что где-то есть проблема, причем очень труднонаходимая.


Чтобы максимально сузить круг поиска, я написал простейший тест памяти, который запускался сразу после сброса процессора, без инициализации всех ненужных устройств типа таймера и т.д. В принципе, индикацию ошибки памяти я воспринял с облегчением – по крайней мере, проблема была явно в железе. Осталось дело за малым – понять, в каком именно месте. И вот с этим оказалось все совсем не просто.

Дело в том, что вообще схема, задействованная в процессе тестирования памяти, по своей сути довольно примитивна. Задействован минимум логики, кроме процессора нет никаких других сложных программируемых элементов. В результате после какого-то времени, затраченного на анализ схемотехники, у меня появилась более-менее уверенность в том, что дело не в принципиальной ошибке в схеме, а в чем-то более случайном – например, в помехах.


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


К сожалению, очередной запуск теста показал, что проблема не ушла. Возможно, она стала проявляться чуть реже, но это сказать трудно – и раньше сбой мог возникнуть то через 20-30 минут, то через несколько часов. Сейчас же, как минимум, оставленная на ночь плата утром оказывалась гарантированно сбойнувшей. В отчаянии я снова вернулся к анализу схемотехники и еще более внимательному изучению диаграмм работы шин процессора. В одном месте у меня возникла определенная мысль, и я опять пошел на тот же форум. В ходе обсуждения моей идеи я в очередной раз получил порцию полезных (а иногда и не очень) советов, попробовал реализовать некоторые вещи (в первую очередь, связанные с небольшой задержкой некоторых управляющих сигналов), но на наличие сбоев это не повлияло вообще никак.


В конце дороги отчетливо вырисовывался конкретный такой тупик, поэтому я начал проверять вообще бредовые идеи. В частности, не сбоит ли сама микросхема памяти? Для проверки я сгенерировал прямо внутри FPGA модуль RAM, который и использовал вместо внешней памяти. Честно говоря, на результат я не надеялся – просто делал все, что приходило в голову. Но представьте мое удивление, когда после этого сбои вдруг исчезли! Вообще я даже как-то не был готов к этому, поэтому не совсем понимал, как использовать это знание. В то, что микросхема памяти неисправна, не верилось даже в этот момент. Также была почти полная уверенность, что я работаю с этой микросхемой правильно – по управляющим сигналам там все проще простого. Но факт оставался фактом – с микросхемой сбой гарантированно происходил не позже, чем через несколько часов теста, с внутренней памятью все проработало без сбоев несколько дней, пока мне не надоело.


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

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


Это было совершенно необъяснимо – даже на осциллографе я видел, что адресные сигналы и так держатся больше, чем это в принципе может быть нужно, но факт оставался фактом. После целых выходных разборок я плюнул на это и решил смириться с этим, как с данностью…


Итак, DOS грузилась, многие программы, не требовавшие графического режима, запускались, можно было двигаться дальше. Естественно, возникло желание запустить какую-то игрушку. Но для игрушки, как правило, требуется графика, а ее у меня пока не было. И если для текстового видеоадаптера удалось обойтись малой кровью путем переделки существующего, то для графики с этим было не так просто.


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


Итак, пишем свой собственный CGA адаптер – даже EGA на пару порядков сложнее, так что пока на него замахиваться не будем. В принципе, немножко для начала я все-таки подсмотрел – нашел, фактически, наброски модуля генерирования VGA-развертки. Но это было полтора десятка строчек, да еще и не до конца работающих. Так что, реально, они были использованы как шаблон для начала писанины – морально так было легче.


Естественно, CGA монитора у меня нет и не планировалось, поэтому идея заключалась в использовании режима VGA 640х400, в которых превосходно ложился CGA-шный режим 320х200 путем простого дублирования точек как по горизонтали, так и по вертикали.

Вообще графический адаптер у меня получился неожиданно легко – мозг к этому моменту вдруг научился мыслить категориями VHDL, плюс появилось небольшое понимание, что можно требовать от VHDL, а чего не стоит. Вообще основное время отладки у меня занял поиск совершенно глупой ошибки, связанной с разрядностью чисел (две такие проблемы наложились друг на друга и дали весьма забавный вариант). В остальном же я начал получать удовольствие от того, как строчки в редакторе превращаются в практически реальное «железо» внутри FPGA и делают именно то, что я хочу.


В самом начале, естественно, адаптер получился далек от совершенства и совместимости, но Checkit смог опознать его и даже вывести первую тестовую картинку:


image


Кстати, Checkit оказался довольно полезной программой – многие вещи он определял довольно хитрыми способами, что заставляло делать всю конструкцию все более PC-совместимой. А так как Checkit мог проверить все узлы и компоненты, то и совместимость тестировалась тоже для всех частей системы.


После исправления самых явных ляпов (типа видимого на предыдущей фотографии дублирования точки из предыдущего байта) удалось, с некоторым трудом, найти игру, которая вроде даже заработала:


image


Цвета на этой картинке не соответствуют оригинальным – в этот момент переключение палитр еще не было сделано, да и сами цвета вообще не были настроенными.


Попытки найти работающие игры показали, что игровые программы, в большинстве случаев работающие напрямую с «железом», куда требовательнее к совместимости, чем какой-нибудь NC или даже QuickBasic. К счастью, FPGA предоставляла практически неограниченные возможности по выявлению фактов обращения программы к интересующим портам, адресам памяти и т.д. Особенно вместе с тем, что BIOS я тоже мог менять по собственному усмотрению, это давало отличный механизм отладки. Кстати, в какой-то момент (уже не помню точно, когда), заработал и Turbo Debugger, что тоже расширило арсенал отладочных инструментов.


Сразу же стало ясно, что нужно делать хотя бы минимальный таймер 8253. Причем программы пытались использовать таймер не только для звуков (канал 2), а еще и активно перепрограммировали канал 0, изменяя таким образом частоту прерываний от таймера, а также использовали этот канал для определения временных параметров.


Почитав документацию к 8253, мне стало немного тоскливо. Делать нужно было много и не очень интересно. Решив заняться этим как-нибудь потом, в тот момент просто залез на все тот же opencores и стащил пару модулей таймера. Один на Verilog, причем весьма упрощенный, второй – по виду крайне навороченный, да еще и на VHDL. К сожалению, таймер на VHDL подключался по шине Wishbone – это открытый стандарт для разработок на FPGA. С Wishbone я до этого никогда не сталкивался, так что решил для начала использовать модуль на Verilog’е, который по интерфейсу выглядел попроще.


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


Дальше же мне пришлось принять кардинальное решение. До этого момента INT 10h я писал сам. В текстовом режиме с этим еще можно было смириться, но вот необходимость поддерживать эти функции в графических режимах меня расстроила. Учитывая, что к этому моменту страсть к программированию на ассемблере была практически удовлетворена (все-таки сказалось то, что в свое время уже пришлось делать это в промышленных объемах), поступил по принципу «Если гора не идет к Мухаммеду, то тот посылает ее нафиг». А именно решил сделать свой CGA адаптер настолько совместимым по «железу», чтобы оригинальный BIOS мог работать с ним.


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


Почему-то процесс приведения в порядок видеоадаптера мне показался очень увлекательным, и в конечном итоге этот узел оказался самым проработанным с точки зрения совместимости с оригинальным устройством. Попутно были добавлены недостающие вещи типа переключаемых палитр, режима 640х200 и т.д. Кстати, для тестирования режима 640х200 оказалось довольно непросто найти программу, поддерживающую данный режим. Единственное, что удалось раскопать, это шахматы:


image


На мой взгляд, выглядит довольно красиво…


Оригинальный обработчик INT 10h отнёсся к такому адаптеру весьма дружелюбно, а я вздохнул с облегчением от отсутствия необходимости писать вещи типа распознавания символа, напечатанного в определенном месте экрана в графическом режиме.


Последним препятствием на пути к приемлемой совместимости с PC была, как ни странно, клавиатура. Хотя это было чуть ли не первое, что я прикрутил к проекту, но с точки зрения совместимости там вообще еще конь не валялся. Основная проблема заключалась в том, что все нормальные программы работают с первым набором скан-кодов, который применялся еще в IBM PC. А вот все клавиатуры, начиная с PC AT, выдают, как минимум, второй набор скан-кодов, очень отличающийся от первого. Только контроллер клавиатуры внутри компьютера преобразовывает эти коды в оригинальный, первый набор, и все обычные программы работают именно с ним (даже если эти программы вроде бы обращаются к клавиатуре напрямую, не используя BIOS). У меня же, естественно, никакого контроллера не было (кстати, в PC AT и даже в поздних PC XT для этого использовался отдельный микроконтроллер на базе 8051). Функции INT 09/16 у меня были реализованы в самом минимальном варианте, а уж о прямой работе программ с клавиатурой вообще и речи не могло быть – они (программы) просто не поняли бы ни одного скан-кода.


К этому моменту я вдруг почувствовал эйфорию от владения VHDL – мне казалось, что я уже постигнул истину, и могу вообще все. Поэтому без промедления был написан изящный (как мне казалось) модуле на VHDL, который выполнял перекодирование скан-кодов. В этом модуле все было очень красиво и хорошо, за исключением одной маленькой детали – он не работал. Причем причину неработоспособности я понять никак не мог, что расстраивало и вызывало недоумение – там всего был десяток строчек.


Очередной раз обратившись на форум к знатокам, я получил изрядное количество действительно толковых советов. Мало того, мое понимание самой концепции VHDL очередной раз чуть ли не в корне поменялось (в т.ч., появилось некоторое разочарование). Основное – чудес не бывает. VHDL (а также все другие HDL) не сделает того, что невозможно сделать обычным способом из имеющихся аппаратных ресурсов. Если я пишу строчку, вроде правильную с точки зрения синтаксиса языка, но при этом даже близко не представляю, как это может быть реализовано в железе, то, скорее всего, оно и не будет реализовано при компиляции. Как минимум, не будет делать то, что от этого требуется. И еще – очень важно использовать шаблоны. Оказывается, многие конструкции языка превращаются в правильные аппаратные узлы только тогда, когда компилятор распознает соответствующий шаблон. Определенная гибкость, конечно, присутствует, но все-равно нужно всегда помнить о рекомендованных стилях описания тех или иных узлов.


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


Короче говоря, сделав модуль преобразователя чуть менее красивым, но зато намного более правильным, получил на его выходе коды в первом наборе. Далее осталось скормить эти коды уже оригинальному обработчику INT 09h, и проверить все тем-же Checkit’ом правильность распознавания нажатий клавиш. Итак, клавиатура тоже была почти 100% совместима на аппаратном уровне.


К этому моменту я начал ощущать все больше и больше неудобств от того, что верхним уровнем проекта у меня все еще оставался схемный дизайн. Окончательным толчком, побудившим взяться за полный переход на VHDL, послужила смена домашнего компьютера. На столе у меня оказался iMac Retina с установленным Windows. К сожалению, Quartus попал в число программ, которые оказались совершенно не готовы к работе с таким разрешением экрана. Схемный дизайн стал совершенно нечитаемым, и никакие мои попытки что-то настроить никаких реальных улучшений не произвели. Деваться было некуда, я стиснул зубы и взялся за текстовый редактор.


Как ни странно, все прошло более, чем гладко. Сейчас уже даже не помню, нужно ли было хоть что-то отлаживать, или же все заработало сразу после переделки. В любом случае, серьезных затыков точно не было, а вот работать сразу стало намного удобнее и эффективнее. Я сразу вспомнил советы ряда знающих людей, настоятельно рекомендовавших мне с самого начала забыть о схемном дизайне и сразу начинать с VHDL/Verilog. Кстати, относительно VHDL vs Verilog – пожалуйста, не спорьте со мной, что лучше/хуже, и почему я остановился именно на VHDL. Давайте считать, что мне просто так захотелось, и это практически правда. Больше на эту тему я рассуждать не буду…


При переходе на VHDL был также полностью переделан последний модуль на схемном дизайне – интерфейс SPI. Если помните, он обеспечивал аппаратный прием/передачу только одного байта, причем вокруг этого нужно было произвести целый ряд подготовительных шагов. Вкупе с медленным процессором (и лениво написанным INT 13h) это давало всего около 35% от быстродействия оригинального жесткого диска PC XT (согласно Checkit). Так как я уже практически чувствовал себя гуру VHDL и вообще цифровой электроники, то сразу решил писать не копию имеющегося интерфейса, а модуль, обеспечивающий пакетную передачу.


Правда, с DMA (или, как говорят у нас в России, ПДП) решил не заморачиваться – контроллера DMA еще не было, а браться сразу за два новых модуля не хотелось, потом не разберешься, где именно проблема. Отладка модуля прошла не совсем гладко – пришлось немного повозиться, в том числе активно задействуя цифровые каналы осциллографа в качестве анализатора протокола. Кстати, почему-то в ходе всего процесса я практически забыл, что в состав Quartus’а входит встроенный цифровой анализатор SignalTap, который, наверное, был бы еще удобнее. Возможно, в будущем у меня руки дойдут и до него (еще ни разу не пользовался), но пока мне очень нравится использовать для этого отдельную железку.


Наверное, с учетом нового модуля можно было бы более серьезно переписать INT 13h, но мне было лень, и я отделался только минимально необходимой модификацией. В результате получилось не очень красивое и совсем неэффективное нагромождение, но все равно скорость с новым модулем выросла практически в 5 раз:


image


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

— у меня нет DMA. Оказалось, что нулевой канал DMA (используемый для регенерации памяти на оригинальных PC) также используется некоторыми программами как счетчик для определения коротких временных промежутков. Пришлось эмулировать соответствующую часть счетчиков контроллера DMA

— обычно (но не всегда) при чтении из несуществующей области памяти или порта ввода/вывода считывается байт FF. У меня считывалось наоборот – 00. Это не понравилось программе, которая проверяла таким образом (и более ничем другим) наличие джойстика, после чего решала, что он есть, и что зажаты все кнопки

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

— не связанное с моим компьютером – потратил кучу времени на выяснение причин зависания простейшей старинной игры Paratrooper. Оказалось что хотя игра и старая, но имевшийся у меня файл был сжат самораспаковывающимся архиватором com/exe файлов. Так вот, та часть, которая отвечала потом за распаковку программы при запуске, содержала команду, которая появилась только, начиная с 286 процессора. Неприятность заключалась в том, что данная команда не сильно влияла на процесс распаковки и портила только некоторые байты (меньше одного из тысячи). Пожалуй, на эти разборки я потратил больше всего времени.


Так, потихоньку, практически все игры, которые у меня были, стали запускаться и работать без особых проблем, в некоторые из них я даже попытался сыграть:


image


В ходе запуска многочисленных игр выяснилось, что имеющийся у меня модуль таймера далеко не идеален – в большинстве случаев звуки были не совсем правильными. Решив, что все-равно захочу разобраться с шиной Wishbone, я решил прикрутить таймер на VHDL, о котором уже упоминал ранее. Для начала, почитал описание Wishbone и сваял нечто типа переходника между Wishbone интерфейсом и шиной 8088 – ничего сложного. К сожалению, таймер не заработал. Пришлось снова доставать осциллограф и смотреть, что же там происходит (в первую очередь, правильно ли формируются Wishbone сигналы).


Кто мог подумать, что в этот момент меня будет ждать великое открытие… Помните, как я мучился со сбоями памяти, и вынужден был ввести промежуточный регистр, необходимости в котором не видел в принципе? Так вот, на экране осциллографа у меня оказалась следующая картинка:


image


Естественно, первое, что бросилось в глаза, так это жуткий звон сигнала 2. Причем звон этот перешел из количественного параметра в качественный. Сигнал 6 формируется одноразрядным счетчиком, на вход которого подан сигнал 2. Фактически, по каждому восходящему фронту сигнала 2 сигнал 6 инвертируется. Но на осциллограмме видно, что сигнал 6 переключился один раз не только по нормальному фронту сигнала 2, но по фронту самого сильного «звона»! Т.е. в моей схеме на некоторых линиях звон был такой амплитуды, что мог вызвать ложные переключения логики. Сказать, что я офигел – не сказать ничего. Даже не верилось, что при всем этом мне удалось добиться устойчивой работы схемы…


Далее, после небольшого анализа схемы с учетом новых данных, мне стало совершенно понятно, где именно возникали старые сбои, и почему тот регистр их вылечил. Тем не менее, нужно было что-то делать, так как именно сигнал 2 мне был нужен для работы с новым модулем таймера. И снова традиционное обращение к знатокам. Из нескольких советов на форуме был выбран вариант с разрезанием дорожки и впаиванием туда резистора. Результат был далек от идеала, но более ложных переключений от звона при тестировании в течение нескольких часов я не зафиксировал:


image


К сожалению, на работоспособность VHDL модуля таймера это не повлияло – он молчал. Повозившись еще некоторое время, причина была обнаружена в довольно неожиданном месте – в самом модуле. Причем была она довольно прозаична (и часто встречающаяся в программировании) – модуль неправильно обрабатывал одно из крайних значений, а именно при делителе 0 он вместо деления на максимальное значение (65536) не делал ничего. Я же проверял все время именно инициализацию канала 0, который инициализируется максимальным делителем, чтобы получить частоту 18.2 Гц. Когда я для эксперимента использовал делитель FFFF, все заработало.


Я даже связался с автором модуля, который (автор) уже и забыл, что он этот модуль писал. Тем не менее, автор помог мне найти конкретное место, где была допущена ошибка, и я даже кое-как попытался ошибку исправить. Именно эта проблема решилась, но были обнаружены другие, так что я пока остановился на первой версии модуля, на Verilog.


К этому моменту готовность моей конструкции была такова, что я созрел для главного эксперимента. Дело в том, что в далеком 86 году я читал статью из журнала «В мире науки», который является русским переводом американского журнала «Scientific American», в которой рассказывалось о новейшем продукте компании Microsoft – а именно об игре MS Flight Simulator. Учитывая, что уже в том время я фанател от компьютеров, но при этом твердо собирался стать летчиком, можно понять, какие эмоции тогда бурлили у меня в голове (да и в других частях тела).


И вот сейчас, спустя почти 30 лет, у меня появилось неутолимое желание запустить именно тот исторический Flight Simulator на моем компьютере. Интерес подогревался еще и тем, что вроде бы в те времена для тестирования на совместимость чуть ли не официально использовались две программы – тот самый Flight Simulator, а также Lotus 1-2-3. Говорилось, что они так плотно используют аппаратные особенности компьютера, что если заработали эти программы, то все остальное и подавно будет работать.


Вообще у меня были некоторые сомнения – я все-таки знал о некоторых подводных камнях в моей конструкции, но все же решил рискнуть (особенно учитывая, что ничем, естественно, не рисковал). Результат на экране:


image


Кстати, загадочная зернистость картинки вначале вызвала у меня подозрение – я сразу стал думать о каком-то совсем хитром способе работы с видеоадаптером, который у меня не поддерживается. На самом деле, как оказалось, таким образом Microsoft пытался получить дополнительные цвета, комбинируя точки из имеющихся цветов. Должен заметить, что, учитывая разрешение 320х200, результат был, мягко говоря, сомнительный.


Никаких проблем с запуском Lotus 1-2-3 тоже не возникло, так что на этом эксперимент можно было бы считать оконченным. Тем не менее, я провел еще ряд небольших доделок и подкруток, после чего стали запускаться и абсолютно нормально работать вообще все программы, которые у меня есть на настоящий момент. Единственной новой функцией, которую я добавил после этого, была EMS. Мне просто не давало покоя, что пропадает больше мегабайта доступной памяти (если честно, то просто хотелось еще что-то сделать), поэтому я нашел описание платы EMS с драйвером, и написал модуль, эмулирующий работу этой платы. Драйвер успешно память опознал:


image


Совсем последним штрихом стала переделка самой процессорной платы. Мне совершенно не нравился тот кошмар, что творился с формой сигналов, а также хотелось еще раз попрактиковаться с Eagle. В результате была разведена 4-слойная печатная плата, у которой один из внутренних слоев был выделен под землю, второй – под оба напряжения питания. Кроме того, самым существенным моментом было устранение шлейфов – разъемы установлены так, что моя плата прямо втыкается в отладочную плату FPGA (если быть совсем уж точным, то в плату расширения портов GPIO отладочной платы FPGA – такая вот матрешка):


image


Были также некоторые схемотехнические изменения – убран полностью формирователь тактовой последовательности 8284 (решил, что можно без проблем убрать его внутрь FPGA, не нанеся ни малейшего ущерба совместимости по сигналам шины) и регистр-защелка на шине адреса/данных (также убран внутрь FPGA). Быстрая проверка формы сигналов на новой плате показала, что сигналы стали практически идеальными:


image


Итак, путь мигающего светодиода на беспаечной макетке до вполне нормального компьютера был пройден за пару месяцев, при этом получено огромное количество удовольствия, а также знаний в целом ряде областей. На выходе получился компьютер с довольно неплохой совместимостью с IBM PC, на котором вообще без замечаний работают все программы, которые я не поленился раздобыть, в т.ч. и те, которые считаются крайне требовательными к совместимости «железа». На компьютере практически полностью (за исключением обработчика INT 13h) используется BIOS 3-ей версии от IBM PC.


Что делать со всем этим (и делать ли вообще что-либо) – не совсем уверен. В ходе процесса очередной раз стало понятно, что, хотя на энтузиазме и эрудиции тоже можно кое-чего добиться, формальное знание основ позволило бы все ускорить, избежать многих граблей, а главное – больше сконцентрироваться на творчестве, а не изобретении велосипеда с квадратными колесами. Поэтому пока есть большое желание заполнить каким-нибудь экспресс-методом прорехи (вернее, зияющие дыры) в знании как просто основ электроники и схемотехники вообще, так и VHDL в частности. Насколько это получится, увидим – всегда присутствует проблема в мотивации и наличии свободного времени.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.