1
0
mirror of https://github.com/Gehstock/Mist_FPGA.git synced 2026-02-28 17:09:12 +00:00
Files
2021-09-02 21:08:25 +02:00

301 lines
7.1 KiB
Systemverilog

//
// Decoder for .CIN and K7 formats based on MAME/MESS hect_tap.cpp written by JJ Stacino
//
// See https://github.com/mamedev/mame/blob/master/src/lib/formats/hect_tap.cpp
//
// The .K7 format appears to be identical to the .CIN format used by the
// Virtual Interact emulator created by "James The Animal Tamer"
//
// The Interact stores data on tape by using a "bit banged" machanism to drive square
// waves of with periods of different duration for zero, one, and gap bits.
//
// At the start of the tape, a set of gap bits is read. A minimum of 356 gap bits
// must be found. Following the initial gaps, the tape data is read.
//
// Data is orgaanized into records. Each record starts with a length byte followed
// by up to 256 additional bytes.
//
// Records with a length of 5 are special command records. The last byte of the
// command record is the command byte. A value of FE indicates a fill command
// and the following record contains the data to use to fill. A value of FD
// indicates end of file.
//
// At the end of the tape is a stop ID that consists of:
// 0 - gap - 0 - gap
//
//
module cassette(
input clk,
input rst_n,
input play,
input rewind,
input motor,
output reg [15:0] tape_addr,
input [7:0] tape_data,
input [15:0] tape_end,
output playing,
output reg flux,
output reg [15:0] audio
);
localparam GAP_CLK = 14'd12500;
localparam ZERO_CLK = 14'd4383;
localparam ONE_CLK = 14'd8117;
localparam HI = 16'h7FFF;
localparam LO = 16'h8000;
localparam START_GAP_DURATION = 10'd764;
localparam RECORD_GAP_DURATION = 10'd4;
localparam WAIT_GAP_DURATION = 10'd150;
parameter
IDLE = 3'd0,
START = 3'd1,
START_RECORD = 3'd2,
WRITE_RECORD_LENGTH = 3'd3,
WRITE_RECORD_DATA = 3'd4,
FINISH = 3'd5;
reg [2:0] state;
reg wr_cycle;
reg wr_gap;
reg wr_byte;
reg [13:0] cycle_high_count;
reg [13:0] cycle_low_count;
reg [9:0] gap_counter;
reg [7:0] byte_output;
reg [7:0] byte_output_index;
reg [8:0] record_size;
reg [7:0] previous_record;
reg prev_play;
reg prev_rewind;
reg prev_motor;
assign playing = state !== IDLE;
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b0, 1'b0, IDLE};
cycle_high_count <= 14'b0;
cycle_low_count <= 14'b0;
gap_counter <= 10'b0;
byte_output <= 8'b0;
byte_output_index <= 8'b0;
record_size <= 9'b0;
previous_record <= 8'b0;
tape_addr <= 16'b0;
audio <= 16'b0;
flux <= 1'b0;
prev_play <= 1'b0;
prev_rewind <= 1'b0;
prev_motor <= 1'b0;
end
else
begin
casez ({wr_cycle, wr_gap, wr_byte, state})
{1'b1, 1'b?, 1'b?, 3'b???}:
begin
if (cycle_high_count)
begin
audio <= HI;
//flux <= 1'b1;
cycle_high_count <= cycle_high_count - 14'd1;
{wr_cycle, wr_gap, wr_byte, state} <= {1'b1, wr_gap, wr_byte, state};
end
else if (cycle_low_count)
begin
audio <= LO;
//flux <= 1'b0;
cycle_low_count <= cycle_low_count - 14'd1;
{wr_cycle, wr_gap, wr_byte, state} <= {1'b1, wr_gap, wr_byte, state};
end
else
begin
flux <= ~flux;
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, wr_gap, wr_byte, state};
end
end
{1'b0, 1'b1, 1'b0, 3'b???}:
begin
if (gap_counter)
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b1, 1'b1, 1'b0, state};
cycle_high_count <= GAP_CLK;
cycle_low_count <= GAP_CLK;
gap_counter <= gap_counter - 10'd1;
end
else
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b0, 1'b0, state};
end
end
{1'b0, 1'b0, 1'b1, 3'b???}:
begin
if (byte_output_index)
begin
if (byte_output & byte_output_index)
begin
cycle_high_count <= ONE_CLK;
cycle_low_count <= ONE_CLK;
end
else
begin
cycle_high_count <= ZERO_CLK;
cycle_low_count <= ZERO_CLK;
end
{wr_cycle, wr_gap, wr_byte, state} <= {1'b1, 1'b0, 1'b1, state};
byte_output_index <= byte_output_index << 1;
end
else
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b0, 1'b0, state};
end
end
{1'b0, 1'b0, 1'b0, IDLE}:
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b0, 1'b0, IDLE};
tape_addr <= 16'b0;
audio <= 16'b0;
flux <= 1'b0;
previous_record <= 8'b0;
end
{1'b0, 1'b0, 1'b0, START}:
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b1, 1'b0, START_RECORD};
tape_addr <= 16'b0;
audio <= 16'b0;
flux <= 1'b0;
previous_record <= 8'b0;
gap_counter <= START_GAP_DURATION;
end
{1'b0, 1'b0, 1'b0, START_RECORD}:
begin
if (tape_addr <= tape_end)
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b1, 1'b0, WRITE_RECORD_LENGTH};
if (previous_record === 8'hFE)
gap_counter <= WAIT_GAP_DURATION;
else
gap_counter <= RECORD_GAP_DURATION;
previous_record <= byte_output;
end
else
begin
state <= FINISH;
end
end
{1'b0, 1'b0, 1'b0, WRITE_RECORD_LENGTH}:
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b0, 1'b1, WRITE_RECORD_DATA};
byte_output <= tape_data;
byte_output_index <= 8'b1;
record_size <= (tape_data == 8'd0 ? 9'd256 : {1'b0, tape_data});
tape_addr <= tape_addr + 16'd1;
end
{1'b0, 1'b0, 1'b0, WRITE_RECORD_DATA}:
begin
if ((tape_addr <= tape_end) && (|record_size))
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b0, 1'b1, WRITE_RECORD_DATA};
byte_output <= tape_data;
byte_output_index <= 8'b1;
tape_addr <= tape_addr + 16'd1;
record_size <= record_size - 9'd1;
end
else if (tape_addr > tape_end)
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b0, 1'b0, FINISH};
end
else
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b0, 1'b0, START_RECORD};
end
end
{1'b0, 1'b0, 1'b0, FINISH}:
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b0, 1'b1, IDLE};
byte_output <= 8'b0;
byte_output_index <= 8'b1;
end
default:
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b0, 1'b0, IDLE};
end
endcase
prev_play <= play;
if (play && !prev_play)
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b0, 1'b0, START};
end
prev_rewind <= rewind;
if (rewind && !prev_rewind)
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b0, 1'b0, IDLE};
end
// for enabling the tape to start under software control (color register A, bit 6)
// for now, this will also rewind and restart tape from beginning, since that's
// the likely preferred behavior for the user but many need to instead
// create a pause and resume state to correctly emulate behavior
prev_motor <= motor;
if (motor && !prev_motor)
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b0, 1'b0, START};
end
else if (prev_motor && !motor)
begin
{wr_cycle, wr_gap, wr_byte, state} <= {1'b0, 1'b0, 1'b0, IDLE};
end
end
end
endmodule