Квартус весит на порядок больше - у меня в виртуальной машине с ModelSim осталось только 3 Гб свободного места, поэтому узнать простой и удобный способ запуска симуляции модели QSync в ModelSim будет (надеюсь) быстрее, чем качать и ставить Квартус.
Вид для печати
Я в предыдущем письме добавил архив для ModeiSim, проверил, вроде работает. Но есть вероятность что ему все равно альтеровские библиотеки понадобяться (в Квартус у меня устновлен).
И эта вероятность равна 100%.
Но изменение в config.v значения CONFIG_VM1_CORE_REG_USES_RAM на 0 действительно решает проблему ( мне слабо переделать vm1_run.do, поэтому набор для симуляции: QSync_for_ModelSim.zip надо запускать через создание обычного родного проекта ModelSim ).
http://pic.pdp-11.ru/images/msim5.png
Но вот вопрос - как заставить ModelSim выводить значение линий AD в инверсном ( т.е. нормальном ) виде ?
О, симуляция пошла, замечательно!
Тут простого ответа я не знаю, можно завести дополнительный сигнал, например i_ad[15:0], назначить ему в исходниках инверсию от pin_ad, и смотреть уже его. Или в подпапке <root>/Qbus есть диаграммы регистров адреса (areg) и данных (qreg), они инвертированы, можно сами значения из них брать. Они вполне мышкой перетаскиваются, можно копи-пастить, то есть легко расположить рядом с диаграммой ad.
.
В тактовом генераторе tbench.v есть ошибка из-за которой весь нулевой такт сигнал clk остаётся неизменным, а первый такт ( как и все последующие ) начинается с положительного полупериода :
http://pic.pdp-11.ru/images/msim6.png
Если изменить код так:
Код://_____________________________________________________________________________
//
// Clock generator
//
initial
begin
ena = 1;
clk = 0;
nclk = 0;
forever
begin
`ifdef CONFIG_SIM_DEBUG_MC
$display("clk: %04d", nclk);
`endif
clk = 0;
#(`CONFIG_SIM_CLOCK_HPERIOD);
clk = 1;
#(`CONFIG_SIM_CLOCK_HPERIOD);
nclk = nclk + 1;
end
end
То работа тактового генератора приходит в норму и растактовка исправляется:
http://pic.pdp-11.ru/images/msim7.png
Это не ошибка, такая особенность первого клока в тестбенче не приводит ни к каким принципиальным последствиям - понятие такт очень условно, просто вот так нарисовалась сеточка на диаграмме, и все процессы сдвинулись на 5 нс. Можно банально вертикальную сеточку сделать 5 нс (оно автоматом, просто позумить в окошке), чтобы отметить и фронты и срезы и чуток проскроллить вправо - и разницы вообще глазом не увидеть. Отладочный nclk остался со времен отладки микроавтомата, тогда его фаза имела смысл, но сейчас его уже можно и вообще удалить.
Update: часто бывает что тактовая от PLL формируется, тогда в начальный момент симуляции тактовой частоты вообще нет, появляется со временем и в произвольной фазе, поэтому привязываться к "сеточке" бессмысленно.
Update2: сейчас LSI-11 разбираю, там 4 тактовых сигнала, все тоже должно быть инвариантно, независимо с какого C1-C4 начнется счет.
Update3: обновление 1.4D
- изменена структура каталогов (потихоньку приближаемся к требованиям OpenCores)
- теперь можно перейти в каталог симуляции и запустить в ModelSim (Altera Edition можно Started/Полный) файл run.do. Удалены все зависимости от путей, для проектов Async/Qsync Quartus при этом не нужен, сразу симулируется с полным пакетом диаграмм.
- проект Async не требует для симуляции альтеровских библиотек
- добавлен тест вычисления знаков числа Пи
Сейчас как раз идёт отладка CPP-модели ( методом потактового сравнения содержимого переменных ), поэтому правильная фаза nclk играет важную роль.
- - - Добавлено - - -
Прояснилась причина, по которой CPP-модель не снимала SYNC при снятии RPLY между eval_p() и eval_n().
Сигнал oe_clr_fc использовался в eval_all_n() раньше, чем устанавливался в assign_all(), а поскольку этот сигнал активен только полтакта, то к следующему вызову eval_all_n() сигнал уже снова был сброшен и поэтому SYNC оставался установленным "вечно".
Вообще говоря - используемый в CPP-модели подход методически ложен:
Код:void eval_n()
{
eval_all_n(); // выполняем все always для переднего фронта
assign_all(pMPI); // вычисляем все assignы
}
Методически корректный подход выглядит так:
Код:void eval_n()
{
assign_all(pMPI); // вычисляем все assignы
eval_all_n(); // выполняем все always для переднего фронта
assign_all(pMPI); // вычисляем все assignы
}
А практически правильный подход может быть таким:
- - - Добавлено - - -Код:void eval_n()
{
assign_in(pMPI); // вычисляем все входные зависимости
eval_all_n(); // выполняем все always для переднего фронта
assign_out(pMPI); // вычисляем все выходные зависимости
}
В ходе тестирования модели QSync выяснился интересный момент.
При осуществлении записи сначала выставляются на шину прошлые данные записи и только затем - новые.
Например, при выполнении на модели процессора 1801ВМ1 следующего кода:
Процессор пишет в память начальное содержимое регистров:Код:.ASect
. = 0
MOV R0, (PC)+
NOP
MOV R1, (PC)+
NOP
MOV R2, (PC)+
NOP
MOV R3, (PC)+
NOP
MOV R4, (PC)+
NOP
MOV R5, (PC)+
NOP
MOV SP, (PC)+
NOP
HALT
И при выполнении команды MOV R1, (PC)+ - осциллограмма выглядит так:Код:gpr[0] = 0133000;
gpr[1] = 0100514;
gpr[2] = 0100410;
gpr[3] = 0040000;
gpr[4] = 0100057;
gpr[5] = 0060000;
gpr[6] = 0000011;
http://pic.pdp-11.ru/images/msim8.png
Лог при этом выглядит так:
Код:# [000085]-1- ad_ena: 0 ; pin_ad_out: 000000 ; dout_out: 0 ; qrd: 133000
#
# [000085]-0- ad_ena: 0 ; pin_ad_out: 000000 ; dout_out: 0 ; qrd: 133000
#
# [000086]-1- ad_ena: 0 ; pin_ad_out: 000000 ; dout_out: 0 ; qrd: 133000
#
# [000086]-0- ad_ena: 1 ; pin_ad_out: 133000 ; dout_out: 1 ; qrd: 133000
#
# [000087]-1- ad_ena: 1 ; pin_ad_out: 100514 ; dout_out: 1 ; qrd: 100514
#
# [000087]-0- ad_ena: 1 ; pin_ad_out: 100514 ; dout_out: 1 ; qrd: 100514
#
# [000088]-1- ad_ena: 1 ; pin_ad_out: 100514 ; dout_out: 1 ; qrd: 100514
#
# [000088]-0- ad_ena: 1 ; pin_ad_out: 100514 ; dout_out: 1 ; qrd: 100514
Интересно, как выглядит аналогичная осциллограмма при выполнении записи реальным 1801ВМ1.
Совсем так точно не выглядит - в реальной системе RPLY побыстрее ставится и снимается, а процессор ведет себя точно также - выставляет/снимает сигналы в той же фазе (ну еще задержку надо учитывать, примерно 10-15нс вносят буфера, остальное - родная задержка Tco микросхемы ВМ1). И достоверные данные появляются на шине одновременно со спадом DOUT: [http://s019.radikal.ru/i640/1512/28/ba14c1dfe198t.jpg]
Update: в строке 1475 vm1_qbus.v можно сменить полярность pin_clk_n на pin_clk_p, тогда достоверные данные записи будут выставляться на шине за полтакта до DOUT. При этом граничная частота проекта на реальном Циклоне-3-С8 падает с 90МГц до 72МГц. Наверное, придется пожертвовать. Версии на Wishbone эта проблема не касается.
Update2: а вообще, не так плохо все, на speedgrade C6 частота падает до 95МГц всего, после тестирования изменение можно внести постоянно.
При этом получается, что assign_all(pMPI); перед обоими eval_all_x(); избыточен, поскольку eval_n() и eval_p() вызываются последовательно друг за другом. Развёрнутая цепочка вызовов функций внутри цикла вызовов последовательности eval_n() и eval_p() выглядит так.
Именно так выглядит цикл, генерируемый Верилатором.Код:{
assign_all(pMPI);
eval_n();
eval_p();
assign_all(pMPI);
} init
eval_n();
assign_all(pMPI);
eval_p();
assign_all(pMPI);
eval_n();
assign_all(pMPI);
eval_p();
assign_all(pMPI);
.....
Я такой подход реализовать не смог. Т.к. у меня не получилось сделать обёртку vm1.v над vm1_qbus.v, такая модель не получалась работоспособной в принципе, пришлось разворачивать их в одну большую плоскую функцию. При этом получилось так, что входные зависимости (ноги, у которых тип inout), зависят от выходных в текущей итерации. Поэтому функция assign_all у меня минимум двухпроходная. И изредка выполняется за три прохода, из-за изменения данных в переменной pin_ad_n.
Это ошибочное впечатление, потому что на самом деле там ещё есть вызов eval(pMPI) ( который делается в tboard.cpp ) и полная последовательность вызовов выглядит так:
А должна выглядеть так:Код:eval_n();
assign_all(pMPI);
eval(pMPI)
eval_p();
assign_all(pMPI);
eval(pMPI)
eval_n();
assign_all(pMPI);
eval(pMPI)
eval_p();
- - - Добавлено - - -Код:eval_n();
assign_all(pMPI);
eval(pMPI)
assign_all(pMPI);
eval_p();
assign_all(pMPI);
eval(pMPI)
assign_all(pMPI);
eval_n();
assign_all(pMPI);
eval(pMPI)
assign_all(pMPI);
eval_p();
Если добавить в проект Верилятора обработку tbench.v, то результат будет другим ( из-за учёта влияния памяти и устройств на состояние сигналов шины между вызовами ).
Можно сделать две копии assign_all(pMPI) и выкинуть из первой все зависимости от сигналов, которые не изменяются памятью и устройствами ( типа BSY, SYNC, DIN, DOUT ), а из второй - от сигналов, которые не изменяет процессор ( типа IRQ1, IRQ2, IRQ3, VIRQ, DMR ).
- - - Добавлено - - -
В реале данные выставляются на одном полутакте с DOUT, поэтому лучше задержать на полтакта активацию pin_ad_ena.