Технологии древних: ATAPI IDE, часть первая, подготовительная

от автора

Однажды я захотел себе сделать PC уровня не старше первого Pentium для DOS и Win9x игр. Причём, я хотел это организовать не как ещё один огромный ящик на столе, а в формате обычной приставки к телевизору. Я нашел материнскую плату формата Baby-AT так называемую Super7, это материнская плата для Socket-7 на стероидах: у неё память уже SDRAM, есть AGP и большой кэш второго уровня. Нашёл корпус для mITX, куда Baby-AT может быть размещена и, самое главное, блок питания там PicoPSU, что позволяет использовать стандартный блок питания от ноутбука. В качестве HDD я использовал IDE SSD, которые достаточно дешёвые. Пока испытывал эту связку на столе я использовал IDE ATAPI привод оптических дисков. Всё работало прекрасно. Только вот в маленьком корпусе mITX нет места для 5,25″ привода, а привод нужен, ибо даже для DOS игры были с поддержкой CD, не говоря о Win9x. И вот тут я понял — пора делать свой IDE ATAPI эмулятор оптических дисков, компактный и бесшумный. Именно об этом и будет этот небольшой цикл статей, который, я надеюсь, доведу до логического конца. Если интересно — заходите, вместе веселее!


Сначала я захотел полностью погрузиться в тему IDE. Я уже имел дело с интерфейсом в любительских поделках, причём как с HDD, так и с ATAPI, правда, последнее только на уровне организации CD плеера 20 лет назад. Имеются базовые представления об интерфейсе, но чтобы замахнуться на сам эмулятор привода нужно копать глубже. Разжился стандартами на ATA/ATAPI, начиная с номерного первого и по седьмой. Кстати, надстройка ATAPI — ATA Packet Interface — появилась у ATA — AT Attachment — только в четвёртой номерной версии 1996 года, до этого оптические приводы не подключались к IDE, но сами приводы существовали и подключались в свои проприетарные разъёмы своих контроллеров, которые обычно были совмещены со звуковыми платами.

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

Пример загрузки PC с диска в режиме PIO

Пример загрузки PC с диска в режиме PIO

Оказалось, что при всём разнообразии декодеров в LA конкретно под IDE/ATAPI декодера нет. Терять время на изучение Python и API для написания своего декодера мне не хотелось, поэтому я поступил иначе. Файл сеанса логического анализатора *.dsl это обычный ZIP архив и если его переименовать в *.zip, то можно распаковать. Внутри находятся папки по одной на каждый канал с чанками данных, а рядом с ними находится обычный текстовый файл с настройками и именами каналов. Я быстро написал программу, которая грузит лог в ОЗУ и шерстит его согласно сигналам IDE, информацию о транзакциях которых я почерпнул из вышеупомянутых стандартов. А потом каждую транзакцию отформатировал в текстовый файл для облегчения понимания происходящего. В итоге получилось вот так:

Вывод программы-анализатора.

Вывод программы-анализатора.
В результате я получил текстовый файл подобного содержания.
RESET 238885 (4777700ns)  INTRQ DEASSERT 32927848 (658556960ns)  WRITE  24 (480ns) CS0 ADR=2 SECTOR COUNT Data  0A READ   24 (480ns) CS0 ADR=2 SECTOR COUNT Data  0A WRITE  24 (480ns) CS0 ADR=2 SECTOR COUNT Data  05 READ   24 (480ns) CS0 ADR=2 SECTOR COUNT Data  05 READ   23 (460ns) CS0 ADR=7 STATUS Data  00 WRITE  24 (480ns) CS0 ADR=6 CONTROL/DEVICE&HEAD Data  A0 READ   24 (480ns) CS0 ADR=6 ALT STAUS/DEVICE&HEAD Data  A0 READ   24 (480ns) CS0 ADR=7 STATUS Data  00 00 00 WRITE  24 (480ns) CS0 ADR=6 CONTROL/DEVICE&HEAD Data  A0 WRITE  24 (480ns) CS0 ADR=7 COMMAND Data  A1 READ   23 (460ns) CS0 ADR=7 STATUS Data  80 80 80 80 80 INTRQ ASSERT 0 (0ns)  INTRQ DEASSERT 1368 (27360ns)  READ   23 (460ns) CS0 ADR=7 STATUS Data  58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58       58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58  READ   16 (320ns) CS0 ADR=0 DATA Data  85C0 0000 0000 0000 0000 0000 0000 0000 0000 0000 2020 2020 2020 2020 2020 2020 | ....................                   2020 2020 2020 2020 0000 0000 0000 312E 3133 2020 2020 4153 5553 2020 2020 4452 |         ......1.13    ASUS    DR       572D 3138 3134 424C 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 2020 0000 | W-1814BL                      ..       0000 0B00 0000 0400 0200 0006 0000 0000 0000 0000 0000 0000 0000 0000 0000 0007 | ................................       0003 0078 0078 017F 0078 0000 0000 0000 0000 00F8 0210 0000 0000 0000 0000 0000 | ...x.x..x......................       00F8 0210 0210 0000 0000 0000 0000 0000 101F 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................ READ   23 (460ns) CS0 ADR=7 STATUS Data  50 50 WRITE  24 (480ns) CS0 ADR=6 CONTROL/DEVICE&HEAD Data  B0 READ   24 (480ns) CS0 ADR=6 ALT STAUS/DEVICE&HEAD Data  B0 READ   24 (480ns) CS0 ADR=7 STATUS Data  01 01 01 WRITE  24 (480ns) CS0 ADR=6 CONTROL/DEVICE&HEAD Data  B0 WRITE  23 (460ns) CS0 ADR=7 COMMAND Data  A1 READ   24 (480ns) CS0 ADR=7 STATUS Data  81 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01       01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01       01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01       01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01       01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 

Это была команда чтения идентификатора устройства. Она базовая ATA и обязана поддерживаться всеми ATA устройствами. Быстро нашлись и ATAPI команды чтения секторов с данными загрузки.

Результат вывода парсера.
WRITE  23 (460ns) CS0 ADR=6 CONTROL/DEVICE&HEAD Data  A0 READ   23 (460ns) CS0 ADR=7 STATUS Data  51 WRITE  24 (480ns) CS0 ADR=6 CONTROL/DEVICE&HEAD Data  A0 READ   24 (480ns) CS0 ADR=7 STATUS Data  51 WRITE  24 (480ns) CS0 ADR=1 FEATURES Data  00 WRITE  24 (480ns) CS0 ADR=4 CYLINDER LOW Data  FE WRITE  24 (480ns) CS0 ADR=5 CYLINDER HIGH Data  FF WRITE  23 (460ns) CS0 ADR=6 CONTROL/DEVICE&HEAD Data  A0 READ   24 (480ns) CS0 ADR=7 STATUS Data  51 WRITE  23 (460ns) CS0 ADR=7 COMMAND Data  A0 READ   24 (480ns) CS0 ADR=7 STATUS Data  58 READ   24 (480ns) CS0 ADR=2 SECTOR COUNT Data  01 READ   24 (480ns) CS0 ADR=7 STATUS Data  58 WRITE  4 (80ns) CS0 ADR=0 DATA Data  0028 0000 1100 0000 0001 0000 | (........... READ   24 (480ns) CS0 ADR=7 STATUS Data  D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0       D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0       D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 INTRQ ASSERT 0 (0ns)  INTRQ DEASSERT 1 (20ns)  READ   24 (480ns) CS0 ADR=7 STATUS Data  D0 58 58 READ   23 (460ns) CS0 ADR=2 SECTOR COUNT Data  02 READ   23 (460ns) CS0 ADR=7 STATUS Data  58 READ   23 (460ns) CS0 ADR=5 CYLINDER HIGH Data  08 READ   24 (480ns) CS0 ADR=4 CYLINDER LOW Data  00 READ   4 (80ns) CS0 ADR=0 DATA Data  4300 3044 3130 4501 204C 4F54 4952 4F54 5320 4550 4943 4946 4143 4954 4E4F 0000 | .CD001.EL TORITO SPECIFICATION..       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 1800 0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................ 
WRITE  24 (480ns) CS0 ADR=6 CONTROL/DEVICE&HEAD Data  A0 READ   24 (480ns) CS0 ADR=7 STATUS Data  50 WRITE  24 (480ns) CS0 ADR=6 CONTROL/DEVICE&HEAD Data  A0 READ   24 (480ns) CS0 ADR=7 STATUS Data  50 WRITE  24 (480ns) CS0 ADR=1 FEATURES Data  00 WRITE  24 (480ns) CS0 ADR=4 CYLINDER LOW Data  FE WRITE  24 (480ns) CS0 ADR=5 CYLINDER HIGH Data  FF WRITE  24 (480ns) CS0 ADR=6 CONTROL/DEVICE&HEAD Data  A0 READ   23 (460ns) CS0 ADR=7 STATUS Data  50 WRITE  24 (480ns) CS0 ADR=7 COMMAND Data  A0 READ   24 (480ns) CS0 ADR=7 STATUS Data  58 READ   23 (460ns) CS0 ADR=2 SECTOR COUNT Data  01 READ   24 (480ns) CS0 ADR=7 STATUS Data  58 WRITE  4 (80ns) CS0 ADR=0 DATA Data  0028 0000 1801 0000 0001 0000 | (........... READ   24 (480ns) CS0 ADR=7 STATUS Data  D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0       D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0       D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 INTRQ ASSERT 0 (0ns)  INTRQ DEASSERT 105 (2100ns)  READ   24 (480ns) CS0 ADR=7 STATUS Data  58 58 58 READ   24 (480ns) CS0 ADR=2 SECTOR COUNT Data  02 READ   23 (460ns) CS0 ADR=7 STATUS Data  58 READ   24 (480ns) CS0 ADR=5 CYLINDER HIGH Data  08 READ   24 (480ns) CS0 ADR=4 CYLINDER LOW Data  00 READ   4 (80ns) CS0 ADR=0 DATA Data  0001 0000 4443 4920 616D 6567 5420 6F6F 736C 7620 2E30 0031 0000 0000 25F7 AA55 | ....CD Image Tools v0.1......%U.       0088 0000 0000 0004 04E5 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................       0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 | ................................ 
WRITE  24 (480ns) CS0 ADR=6 CONTROL/DEVICE&HEAD Data  A0 READ   24 (480ns) CS0 ADR=7 STATUS Data  50 WRITE  24 (480ns) CS0 ADR=6 CONTROL/DEVICE&HEAD Data  A0 READ   24 (480ns) CS0 ADR=7 STATUS Data  50 WRITE  24 (480ns) CS0 ADR=1 FEATURES Data  00 WRITE  24 (480ns) CS0 ADR=4 CYLINDER LOW Data  FE WRITE  24 (480ns) CS0 ADR=5 CYLINDER HIGH Data  FF WRITE  24 (480ns) CS0 ADR=6 CONTROL/DEVICE&HEAD Data  A0 READ   23 (460ns) CS0 ADR=7 STATUS Data  50 WRITE  24 (480ns) CS0 ADR=7 COMMAND Data  A0 READ   23 (460ns) CS0 ADR=7 STATUS Data  58 READ   23 (460ns) CS0 ADR=2 SECTOR COUNT Data  01 READ   24 (480ns) CS0 ADR=7 STATUS Data  58 WRITE  4 (80ns) CS0 ADR=0 DATA Data  0028 0000 E504 0000 0001 0000 | (........... READ   24 (480ns) CS0 ADR=7 STATUS Data  D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0       D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 D0 INTRQ ASSERT 0 (0ns)  INTRQ DEASSERT 118 (2360ns)  READ   24 (480ns) CS0 ADR=7 STATUS Data  58 58 58 READ   23 (460ns) CS0 ADR=2 SECTOR COUNT Data  02 READ   24 (480ns) CS0 ADR=7 STATUS Data  58 READ   24 (480ns) CS0 ADR=5 CYLINDER HIGH Data  08 READ   23 (460ns) CS0 ADR=4 CYLINDER LOW Data  00 READ   4 (80ns) CS0 ADR=0 DATA Data  33FA 8EE4 BCD4 7C00 50FB 5153 1E52 0656 E857 0464 0A0D 6143 6E6E 746F 62FF 6F6F | .3.....|.PSQR.V.W.d...Cannot.boo       FF74 7266 6D6F 43FF 2E44 50FF 6572 7373 61FF 796E 6BFF 7965 74FF FF6F 6572 6F62 | t.from.CD..Press.any.key.to.rebo       746F 2E2E 002E 97B3 09D9 0052 0426 0675 0159 1529 F976 D55A B66F 5243 67E8 2FA7 | ot........R.&.u.Y.).v.Z.o.CR.g./       47DE 6BF0 58FD 75DC 9E87 4CC7 02DD 7BA1 2092 8306 E756 3095 02A7 E264 AB59 399C | .G.k.X.u...L...{. ..V..0..d.Y..9       48DB F875 3003 81F6 5D9A CEBA E5DC 79A8 96DC B642 7404 B29A CC30 A3D9 D8D7 F65F | .Hu..0...].....y..B..t..0....._.       FB6A DA40 8146 C12A 1329 A77E 931C B864 070D 7C6D A63B 8C2E 2CC7 879E 68ED 55C9 | j.@.F.*.).~...d...m|;....,...h.U       4DB6 8FE7 7FC2 AC9B 61C6 96B4 5932 2CBE CAF6 BD9A 02BB 74EB 7F72 C19B 4693 2CE5 | .M......a..2Y.,.......tr...F.,       CCDC FE30 7B6F BFC7 189A 0BF7 D4FC E5E5 6915 0136 3993 15BF 748A 6EA7 37DC 66C3 | ..0.o{...........i6..9...t.n.7.f       308C 4FA4 ABA7 DDE5 67F2 F7A4 C3A2 85FB 8367 7378 F52E D803 995C 5471 74D3 7E61 | .0.O.....g......g.xs....\.qT.ta~       77F9 FBDE 63FF 62CD EF5E 0DDF 9087 B78D 1819 C341 1FF1 E8A6 69C1 7A23 B3EA 8BC2 | .w...c.b^.........A......i#z....       0FFB 5F14 1FD5 637C 7288 81FC 0799 0A38 5EF2 ACD0 22C7 79E5 830A A7DC 2F18 359F | ..._..|c.r....8..^...".y...../.5       3236 551E E032 D543 33CC 8FBF EB83 C6F7 5B95 70D8 66D1 1E95 FEC4 D800 3100 447C | 62.U2.C..3.......[.p.f.......1|D       3E41 1191 0ECE 7FA9 1CC1 ECB2 D806 0267 BD57 B161 D748 2D27 A12F C54A 74D0 5B9D | A>...........g.W.a.H.'-/.J..t.[ 

Выяснилось, что типов транзакций на шине всего 4. Это чтение и запись в режиме PIO, когда обращение к регистрам ATA происходит как к обычной ячейке ввода-вывода с использованием адреса, сигнала выбора и строба записи или чтения. Так же есть чтение и запись в режиме DMA, здесь используется дополнительный набор сигналов для ускорения доступа. Пришло осознание того, что для создания своего устройства IDE ATAPI будет не лишним свой интерфейс, который позволит формировать все вышеуказанные транзакции, отслеживать сигналы статуса и устанавливать сигналы управления. При этом желательно, чтобы устройству не мешала система, т.е. нельзя располагать его в адресах ввода-вывода стандартных IDE, иначе система монопольно захватит его и будет всячески мешать. Вот именно о таком устройстве дальше речь и пойдёт.

На роль такого устройства напрашивается какой-либо микроконтроллер. Однако я достаточно долго взвешивал все за и против и пришёл к такому мнению: интерфейс 5 вольтовый, при этом сигналы должны продавливать относительно длинный провод. Контроллеры, способные на 16 битные транзакции практически все на 3,3 вольта и для них придётся делать согласование уровней и мощности сигналов, а 5 вольтовые контроллеры в основном 8 битные и для них придётся делать расширитель. Поэтому я пошёл другим путём: я взял CPLD семейства MAX7000S и USB мост от FTDI с параллельным интерфейсом FT245R, которых у меня целый короб, к тому же им нужна минимальная обвязка. Устройство получилось вот таким:

Схема максимально проста.

Схема максимально проста.
Самопал во все поля!

Самопал во все поля!
ЛУТ всё ещё в деле, а LED были добавлены в самом конце.

ЛУТ всё ещё в деле, а LED были добавлены в самом конце.
Ещё фото под спойлером.

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

0000 HHHH - WRITE HEX 0001 .AAA - WRITE REG CS1X 0010 .AAA - WRITE REG CS3X 0011 .... - WRITE DMA 0100 0CRA - SET {CSEL, RESET, DMACK} 0100 1... - READ STATUS 0101 .AAA - READ REG CS1X 0110 .AAA - READ REG CS3X 0111 .... - READ DMA 1.WW WWWW - IDE TIMING

Это команды, которые летят в сторону устройства. Большинство из них не требуют ответа. Как видно, старший ниббл это сама команда, а младший её модификатор. Команда 0x это передача параметра в хекс, т.е. от 0x00 до 0x0F это, по сути передача ниббла от 0x0 до 0xF. Устройство имеет 16ти битный регистр параметра, который при каждой такой команде сдвигается на ниббл влево, а младший ниббл принимает новое значение. Таким образом, чтобы записать байт надо послать 2 команды, а если нужно записать слово то 4. Накопив, таким образом, необходимый параметр можно подать команду записи и данные из регистра параметра улетят в шину IDE. Эдакий G-код наоборот. Команда IDE TIMING позволяет установить ширину строба данных с шагом 20нс. Команды чтения инициируют возврат данных в PC. Формат ответов такой же:

0000 HHHH - READ HEX 0101 .AAA - REG CS1X ACK 0110 .AAA - REG CS3X ACK 0111 .... - READ DMA 1..R PDQI - READ STATE {RESRV, PDIAG, DASP, DMARQ, INTRQ}

Т.е. если это было чтение регистра, то сначала прилетят все необходимые нибблы а потом сам опкод как подтверждение. Так что парсить обратный поток так же просто, как и формировать исходящий. А учитывая что под Windows есть библиотека D2XXX от производителя чипа, которая позволяет формировать большой блок данных, то накладные расходы на USB становятся незначительными. Например, для чтения 256 слов регистра данных (1 сектор в 512 байт) достаточно послать 256 опкодов разом и они прокешируются драйвером и микросхемой, останется дождаться такой же блок ответов.

А теперь приступим к конфигурированию.

Полный код на Verilog.
// module IDE_DIRECT( // Системный inputCLK,// Такты 50МГц // Лампочки outputreg LED_WR,// Лампочка записи в USB outputreg LED_RD,// Лампочка чтения из USB outputreg LED_ACT,// Лампочка активного привода // Шина FTDI inout[7:0]FD,// Шина данных outputreg nRD,// Строб чтения outputreg WR,// Строб записи inputnRXF,// Флаг наличия данных inputnTXE,// Флаг готовности передатчика // Шина IDE inout[15:0]DD,// Шина данных IDE outputreg [2:0]DA,// Шина адреса outputreg CS1X,// Основные регистры outputreg CS3X,// Дополнительные регистры outputreg DIOR,// Сигнал чтения outputreg DIOW,// Сигнал записи outputreg RESET,// Сигнал сброса inoutCSEL,// Сигнал выбора по кабелю inputIORDY,// Сигнал готовности inputINTRQ,// Сигнал запроса прерывания inputDMARQ,// Сигнал запроса DMA outputreg DMACK,// Сигнал подтверждения DMA inputDASP,// inputPDIAG,// inoutRESERV// Резерв );  // Заглушки assign RESERV = 1'bZ;  // Установка сигналов assign CSEL = (CSELR)? 1'b0 : 1'bZ;  // Шины assign FD[7:0] = (~WR) ? 8'hZZ : (STATUS[1]) ? {3'h4,RESERV,PDIAG,DASP,DMARQ,INTRQ} : (STATUS[0]) ? Answer[7:0] : 8'h00; assign DD[7:0] = (~DIOW) ? Param[7:0] : 8'hZZ; assign DD[15:8] = (~DIOW & ~DA[2] & ~DA[1] & ~DA[0]) ? Param[15:8] : 8'hZZ;  // USB -> IDE // 0000 HHHH - WRITE HEX // 0001 .AAA - WRITE REG CS1X // 0010 .AAA - WRITE REG CS3X // 0011 .... - WRITE DMA // 0100 0CRA - WRITE {CSEL, RESET, DMACK} // 0100 1... - READ STATUS // 0101 .AAA - READ REG CS1X // 0110 .AAA - READ REG CS3X // 0111 .... - READ DMA // 1.WW WWWW - IDE TIMING  // IDE -> USB // 0000 HHHH - READ HEX // 0101 .AAA - REG CS1X ACK // 0110 .AAA - REG CS3X ACK // 0111 .... - READ DMA // 1..R PDQI - READ STATE {RESRV, PDIAG, DASP, DMARQ, INTRQ}   // Мультиплексор выгружаемых данных wire [7:0]Answer;  assign Answer[7:0] = (SendCnt[2]) ? {4'h0,DR[15:12]} : (SendCnt[1]) ? (SendCnt[0]) ? {4'h0,DR[11:8]} : {4'h0,DR[7:4]} : (SendCnt[0]) ? {4'h0,DR[3:0]} : OpCode[7:0];  // Сигналы выбора wire WriteHex; wire WriteReg; wire WriteDMA; wire WriteCtrl; wire ReadStat; wire ReadReg; wire ReadDMA;  assign WriteHex  = ~OpCode[7] & ~OpCode[6] & ~OpCode[5] & ~OpCode[4]; assign WriteReg  = ~OpCode[7] & ~OpCode[6] & (OpCode[5] ^  OpCode[4]); assign WriteDMA  = ~OpCode[7] & ~OpCode[6] &  OpCode[5] &  OpCode[4]; assign WriteCtrl = ~OpCode[7] &  OpCode[6] & ~OpCode[5] & ~OpCode[4] & ~OpCode[3]; assign ReadStat  = ~OpCode[7] &  OpCode[6] & ~OpCode[5] & ~OpCode[4] &  OpCode[3]; assign ReadReg   = ~OpCode[7] &  OpCode[6] & (OpCode[5] ^  OpCode[4]); assign ReadDMA   = ~OpCode[7] &  OpCode[6] &  OpCode[5] &  OpCode[4];   // Переменные reg nRXFr = 1'b1;// Синхронизация сигнала nRXF reg nTXEr = 1'b0;// Синхронизация сигнала nTXE reg IORDYr;// Синхронизация сигнала IORDY reg CSELR;// Активация сигнала CSEL на массу ОК // reg [1:0]SubCycle = 2'h0;// Счётчик субцикла reg [7:0]OpCode;// Опкод из USB  reg [15:0]Param;// Параметр команды reg [15:0]DR;// Регистр данных IDE reg [5:0]SetTime;// Длина транзакции IDE reg [5:0]IDETime;// Счётчик времени транзакции IDE reg [1:0]STATUS;// Флаг посылки статуса reg [2:0]SendCnt;// Количество посылаемых данных   // Описание состояний машины reg [3:0]FState;// Состояние машины localparam fsINIT    = 4'h0;// Инициализация всего localparam fsURXF    = 4'h1;// Анализ флага RXF localparam fsUREAD   = 4'h2;// Чтение из USB localparam fsEXECUTE = 4'h3;// Выполнение опкода localparam fsWPREP   = 4'h4;// Подготовка выбранной записи localparam fsWRITE   = 4'h5;// Транзакция записи на шине IDE localparam fsSTATUS  = 4'h6;// Готовим отправить статус в USB localparam fsUWRITE  = 4'h7;// Запись в USB localparam fsUGAP    = 4'h8;// Защитное ожидание между записями localparam fsRPREP   = 4'h9;// Подготовка к чтению localparam fsREAD    = 4'hA;// Транзакция чтения на шине IDE localparam fsSEND    = 4'hB;// Готовимся посылать данные в USB  // Чтение опкода always @(posedge nRD) begin // Сохраняем данные по фронту чтения опкода OpCode[7:0] <= FD[7:0]; end  // Чтение данных с IDE always @(posedge DIOR) begin DR[15:0] <= DD[15:0]; end  // Синхронная логика always @(posedge CLK) begin // Синхронизация сигналов nRXFr <= nRXF; nTXEr <= nTXE; IORDYr <= IORDY;  // Лампочки LED_ACT <= DASP; LED_RD <= nRXF; LED_WR <= ~nTXE;  // Счётчик субцикла SubCycle[1:0] <=  {SubCycle[0] & ((FState == fsUREAD) | (FState == fsUWRITE) | (FState == fsUGAP)),~SubCycle[1] & ((FState == fsUREAD) | (FState == fsUWRITE) | (FState == fsUGAP))};  // Машина состояний case (FState[3:0]) // Инит fsINIT : begin // Инит всех переменных nRD <= 1'b1; WR <= 1'b0; DA[2:0] <= 3'h0; CS1X <= 1'b0; CS3X <= 1'b0; DIOR <= 1'b1; DIOW <= 1'b1; CSELR <= 1'b0; RESET <= 1'b0; DMACK <= 1'b1; // Снимаем флаги STATUS <= 1'b0; // Начинаем работу FState <= fsURXF; end // Анализ наличия данных в USB fsURXF : if (~nRXFr) FState <= fsUREAD; else FState <= fsURXF; // Вычитываем USB fsUREAD : begin // Сигнал чтения nRD <= SubCycle[1] & ~SubCycle[0]; // Заканчиваем if (SubCycle[1] & ~SubCycle[0]) FState <= fsEXECUTE; end // Исполняем опкод fsEXECUTE : begin // Это установка тайминга? if (OpCode[7]) SetTime[5:0] <= OpCode[5:0]; else // Это HEX? if (WriteHex) Param[15:0] <= {Param[11:0],OpCode[3:0]}; else // Это сигналы управления? if (WriteCtrl) {CSELR,RESET,DMACK} <= OpCode[2:0]; // Запись в IDE if (WriteReg | WriteDMA) FState <= fsWPREP; else // Чтение состояние входов if (ReadStat) FState <= fsSTATUS; else // Чтение из IDE if (ReadReg | ReadDMA) FState <= fsRPREP; else // Иначе ходим на выход FState <= fsURXF; end // Подготавливаем запись в IDE fsWPREP: begin // Настройки для PIO if (WriteReg) {DMACK,CS1X,CS3X,DA[2:0]} <= {1'b1,OpCode[5:4],OpCode[2:0]}; // Настройки для DMA if (WriteDMA) {DMACK,CS1X,CS3X,DA[2:0]} <= 6'h18; // Устанавливаем сигналы if (~WriteReg & ~WriteDMA) FState <= fsURXF; else begin // Активируем запись IDETime[5:0] <= SetTime[5:0]; DIOW <= 1'b0; FState <= fsWRITE; end end // Транзакция записи в IDE fsWRITE : if (IDETime[5:0] == 6'h00) begin // Выключаем записи DIOW <= 1'b1; DMACK <= 1'b1; // Уходим FState <= fsURXF; end else if (IORDYr) IDETime[5:0] <= IDETime[5:0] - 6'h01; // Готовим статус fsSTATUS : begin // Устанавливаем флаг статуса STATUS[1] <= 1'b1; FState <= fsUWRITE; end // Посылаем в USB fsUWRITE : begin // Сигнал чтения WR <= ~(SubCycle[1] & ~SubCycle[0]); // Заканчиваем if (SubCycle[1] & ~SubCycle[0]) begin // Посылается статус? if (STATUS[1]) begin STATUS[1] <= 1'b0; FState <= fsURXF; end else // Посылаются данные из IDE? if (STATUS[0]) begin // Уже всё послали? if (~SendCnt[2] & ~SendCnt[1] & ~SendCnt[0]) begin // Снимаем статус STATUS[0] <= 1'b0; // Уходим FState <= fsURXF; end else FState <= fsUGAP; end end end // Защитный интервал между записями fsUGAP : if (SubCycle[1] & ~SubCycle[0] & ~nTXEr) begin // Считаем нибблы SendCnt[2:0] <= SendCnt[2:0] - 3'h1; // Возвращаемся к записи, если USB свободен FState <= fsUWRITE; end // Подготовка к чтению из IDE fsRPREP : begin // Настройки для PIO if (ReadReg) {DMACK,CS1X,CS3X,DA[2:0]} <= {1'b1,OpCode[5:4],OpCode[2:0]}; // Настройки для DMA if (ReadDMA) {DMACK,CS1X,CS3X,DA[2:0]} <= 6'h18; // Устанавливаем сигналы if (~ReadReg & ~ReadDMA) FState <= fsURXF; else begin // Активируем чтение IDETime[5:0] <= SetTime[5:0]; DIOR <= 1'b0; FState <= fsREAD; end end // Транзакция чтения из IDE fsREAD : if (IDETime[5:0] == 6'h00) begin // Выключаем записи DIOR <= 1'b1; DMACK <= 1'b1; // Уходим FState <= fsSEND; end else if (IORDYr) IDETime[5:0] <= IDETime[5:0] - 6'h01; // Готовимся посылать байты в USB fsSEND : begin // Устанавливаем флаг статуса и количество записей STATUS[0] <= 1'b1; if (~DA[2] & ~DA[1] & ~DA[0]) SendCnt[2:0] <= 3'h4; else SendCnt[2:0] <= 3'h2; // Если USB свободен if (~nTXEr) FState <= fsUWRITE; end endcase end  // Выход endmodule 

Отчёт о занятых ресурсах.

Отчёт о занятых ресурсах.
В симуляторе тоже всё нормально.

В симуляторе тоже всё нормально.

Настало время проверить, что получилось. Для этого сначала надо подключить логический анализатор к нашему интерфейсу. Я воспользовался макеткой, которую использовал для подключения к IDE материнской платы.

Быстренько написал простенькую программу для связи с интерфейсом и проверил базовые команды IDE:

Идентификация успешно прочиталась.

Идентификация успешно прочиталась.
А вот так оно выглядит в анализаторе.

А вот так оно выглядит в анализаторе.

Похоже, что концепт реально работает и можно развивать управляющую программу в сторону добавления всех необходимых функций управления устройствами IDE ATA/ATAPI. Это устройство не для скоростного переноса данных, но, тем не менее, позволяет получать данные с разных IDE устройств. Можно подключить даже HDD. В теории даже можно организовать пассивный переходник на ISA и подключить ESDI контроллер с MFM HDD вроде ST-225.

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


ссылка на оригинал статьи https://habr.com/ru/articles/896234/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *