mirror of
https://github.com/Gehstock/Mist_FPGA.git
synced 2026-02-28 17:09:12 +00:00
301 lines
7.1 KiB
Systemverilog
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
|
|
|
|
|
|
|