Лисья Нора

Оглавление


§ Устройство TAP

Наверняка многие из тех, у кого был Спектрум, видели полоски при загрузке или сохранении программ. Сегодня я расскажу, как хранятся данные на магнитной ленте и как устроен формат tap-файла, который хранит эту информацию.
TAP-файл устроен до крайности просто и он предназначен для эмуляторов. Информация в нем хранится в виде блоков. Существуют два типа блоков – это заголовки, которые отвечают за имя файла, его длину, тип, и сами блоки данных. Любой tap-файл состоит из серии таких блоков.
Каждый блок задается двухбайтным числом, который может принимать значение от 3 до 65535. Далее идет сам блок заданного размера. Этот блок содержит в себе данные, которые использует сам спектрум при загрузке, либо сохранении.
Если блок имеет тип "заголовок", то его первый байт отмечается как 0, если же этот блок имеет тип "данные", то первый байт этого блока отмечается как 255, то есть, и только так, хотя на самом деле тут можно указывать и другие числа, к примеру, все что меньше 128 относится к заголовочному типу блока, а то, что более, к блоку с данными, так как имеет значение только старший бит типа блока.
Заголовок всегда содержит в себе 19 байт и его структура такова: первый байт это нуль, второй байт обозначает разновидность блока с данными, которые непременно идут за блоком заголовка. Таких разновидностей не так много, тип 0 – программа для бейсика, тип 1 – массив чисел, тип 2 – массив строк, тип 3 – блок с кодом. Самые частые типы это бейсик-программа и блок с кодом.
Далее идут 10 байт, в которых записано имя файла. Другими словами, имя файла не может быть более десяти байт. После имени файла идут два байта длины блока с данными. Далее следуют два параметра по два байта каждый.
В самом конце идет байт контрольной суммы. Он считается для каждого блока и предназначен для того, чтобы определить, что этот блок загрузился правильно, сравнив контрольную сумму с загруженной информацией. Если сумма не совпадает, спектрум выдает ошибку загрузки с ленты. Контрольная сумма считается просто – это операция исключающего ИЛИ над всем байтам в блоке.
После заголовка идет блок с данными. В тап-файле за блоком следует два байта с длиной следующего блока. В самом же блоке первый байт всегда 255, потом идут данные, тип и длина которых был ранее задан в заголовке. В конце находится байт контрольной суммы.
БайтДлинаОписание
01Здесь 00
11Тип (0,1,2 или 3)
210Имя файла
122Длина блока данных
142Параметр 1
162Параметр 2
182CRC

§ Запись на ленту

Теперь же разберем то, каким образом записываются данные на ленту. На самом деле это довольно просто. Перед записью (или чтением) блока всегда записывается так называемый лидирующий сигнал (пилот). Он представляет из себя тон заданной частоты и предназначен для синхронизации – для того, чтобы при чтении с ленты компьютер знал, где надо начинать считывать непосредственно данные, настроившись на этот лидирующий сигнал. Сигнал перебрасывает цвет бордера с красного на бирюзовый, а также пишет либо 1, либо 0 на выход магнитофона (начиная с 1). Если бордер красный – пишется 0 (низкий сигнал), если бирюзовый – пишется 1 (высокий сигнал).
Длина каждого сигнала отмеряется в тактах процессора. Каждый сигнал (пилотного тона) длится ровно 2168 тактов процессора. Учитывая что за одну секунду выполняется 3.5 млн тактов процессора, то тогда, если разделить 3.5 млн на 2168 и поделить еще на два, поскольку сигналов два (1 и 0), мы получим частоту 807 герц. Именно с такой частотой записывается лидирующий сигнал. Стоит отметить, что для блока с заголовкой сигнал длится ровно 5 секунд, а для блока данных он длится ровно 2 секунды.
После того, как был послан пилотный сигнал, посылаются уровни 1 (высокий) и 0 (низкий – активный уровень) разной длины – или синхросигнал. Сначала посылается 1 длиной 667 тактов, потом посылается 0 длиной 735 тактов. С точки зрения человеческого этот сигнал совершенно неотличим, однако спектрум его отличает.
Далее идет запись байтов. Данные записываются побитно, начиная со старшего бита к младшему. К примеру если записывать байт F0, то вначале будут записаны четыре бита единиц, и потом четыре бита нулей.
Данные кодируются на магнитной ленте путем записи сигналов разной длины, а следовательно, и разной частоты. Один бит это одна переброска из единицу в нуль.

§ Код на верилоге

module tap
(
input wire reset_n,
input wire clock, // Частота процессора
input wire play, // Сигнал запуска ленты =1
output reg mic,
output reg [15:0] tap_address,
input wire [7:0] tap_data
);
 
`ifdef ICARUS
parameter
PILOT_PERIOD = 4,
PILOT_HEADER = 6,
PILOT_DATA = 3,
SYNC_HI = 4,
SYNC_LO = 3,
SIGNAL_0 = 2,
SIGNAL_1 = 4;
`else
parameter
PILOT_PERIOD = 2168,
PILOT_HEADER = 8064,
PILOT_DATA = 3224,
SYNC_HI = 667,
SYNC_LO = 735,
SIGNAL_0 = 855,
SIGNAL_1 = 1710;
`endif
 
reg [ 3:0] state = 0;
reg [11:0] cnt = 0; // 2^12=4096
reg [12:0] pilot = 0; // 8064 | 3224
reg [15:0] length = 0;
reg [10:0] hdata = 0;
reg [10:0] ldata = 0;
reg [ 2:0] bitn = 0;
reg [20:0] delay = 0; // Задержка после header
reg block = 0; // Тип блока
 
initial tap_address = 0;
 
always @(posedge clock) begin
 
if (!reset_n)
begin
 
state <= 0;
mic <= 1;
tap_address <= 0;
 
end
else case (state)
 
// Ожидание "включения магнитофона"
0: begin state <= play ? 1 : 0; mic <= 1; end
// Считывание длины блока
1: begin state <= 2; length[ 7:0] <= tap_data; tap_address <= tap_address + 1; end
2: begin state <= 3; length[15:8] <= tap_data; tap_address <= tap_address + 1; end
// Запись длины блока
3: begin
 
state <= length ? 4 : 15;
block <= tap_data[7];
pilot <= tap_data[7] ? PILOT_DATA : PILOT_HEADER;
delay <= 1750000;
bitn <= 7;
cnt <= 0;
 
end
// for (i = 0; i < pilot; i++) for (j = 0; j < cnt; j++) mic ^= 1;
// Запись пилотного сигнала
4: begin
 
cnt <= cnt + 1;
 
// Если достиг своего периода
if (cnt == PILOT_PERIOD-1)
begin
 
cnt <= 0;
mic <= ~mic;
pilot <= pilot - 1;
 
if (pilot == 1) begin state <= 5; cnt <= SYNC_HI; end
 
end
 
end
// Запись синхросигнала TTTT\___
5: begin mic <= 1; cnt <= cnt - 1; state <= (cnt == 2) ? 6 : 5; end
6: begin mic <= 0; cnt <= cnt + 1; state <= (cnt == SYNC_LO) ? 7 : 6; end
// Считывание бита
7: begin
 
mic <= 1;
bitn <= bitn - 1;
state <= 8;
 
// Вычисление длительности
hdata <= tap_data[ bitn ] ? SIGNAL_1 : SIGNAL_0;
ldata <= tap_data[ bitn ] ? SIGNAL_1 : SIGNAL_0;
 
// Это последний байт в потоке
// Если тип блока 0 то должна быть задержка после header
if (bitn == 7 && length == 0)
state <= (block ? 0 : 10);
 
// Если это младший бит, то следующий будет старший
if (bitn == 0) begin
 
length <= length - 1;
tap_address <= tap_address + 1;
 
end
 
end
 
// Подача сигнала 1710 или 855 (запись данных)
8: begin mic <= 1; state <= hdata == 2 ? 9 : 8; hdata <= hdata - 1; end
9: begin mic <= 0; state <= ldata == 1 ? 7 : 9; ldata <= ldata - 1; end
 
// Задержка после HEADER и начало считывания нового блока с данными
10: if (delay) delay <= delay - 1; else state <= 1;
 
endcase
 
end
 
endmodule