Технологии древних: ATAPI IDE, часть первая, подготовительная
- среда, 2 апреля 2025 г. в 00:00:08
Однажды я захотел себе сделать 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. Результат меня прям порадовал.
Оказалось, что при всём разнообразии декодеров в 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, которых у меня целый короб, к тому же им нужна минимальная обвязка. Устройство получилось вот таким:
Пришло время описать конфигурацию для 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 опкодов разом и они прокешируются драйвером и микросхемой, останется дождаться такой же блок ответов.
А теперь приступим к конфигурированию.
//
module IDE_DIRECT(
// Системный
input CLK, // Такты 50МГц
// Лампочки
output reg LED_WR, // Лампочка записи в USB
output reg LED_RD, // Лампочка чтения из USB
output reg LED_ACT, // Лампочка активного привода
// Шина FTDI
inout [7:0]FD, // Шина данных
output reg nRD, // Строб чтения
output reg WR, // Строб записи
input nRXF, // Флаг наличия данных
input nTXE, // Флаг готовности передатчика
// Шина IDE
inout [15:0]DD, // Шина данных IDE
output reg [2:0]DA, // Шина адреса
output reg CS1X, // Основные регистры
output reg CS3X, // Дополнительные регистры
output reg DIOR, // Сигнал чтения
output reg DIOW, // Сигнал записи
output reg RESET, // Сигнал сброса
inout CSEL, // Сигнал выбора по кабелю
input IORDY, // Сигнал готовности
input INTRQ, // Сигнал запроса прерывания
input DMARQ, // Сигнал запроса DMA
output reg DMACK, // Сигнал подтверждения DMA
input DASP, //
input PDIAG, //
inout RESERV // Резерв
);
// Заглушки
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 команды и обсуждать блок-схему будущего эмулятора оптических дисков. Спасибо за внимание.