SDVD/design/sd/sd_controller.sv
Waylon Cude f840d27b8e
All checks were successful
ci/woodpecker/push/test-workflow Pipeline was successful
Demo commit
The audio output is still messed up, but this commit gets everything as
ready as it can get. Fixed up all the testbenches and added state
machines for everything
2025-06-10 13:26:35 -07:00

444 lines
13 KiB
Systemverilog

/****
* sd_controller.sv - Initializes and reads data off an sd card, feeding it
* into the audio buffer
*
* @author: Waylon Cude, Dilanthi Prentice
* @date: 6/12/25
*
* **/
module sd_controller(
(* MARK_DEBUG = "TRUE" *)
input logic slow_clk,
(* MARK_DEBUG = "TRUE" *)
input logic fast_clk,
input logic crc_clk,
input logic reset,
(* MARK_DEBUG = "TRUE" *)
input logic [3:0] sd_data,
inout logic sd_cmd,
output logic ready,
output wire clk,
audio_buffer_interface.driver buffer
);
// NOTE: this gets encoded as one-hot, even if in here I set it as a logic[5:0]
enum logic [5:0] {
INIT,WAIT,SEND_CMD0,WAIT_CMD0,DELAY_CMD0, // last 4
SEND_CMD8,WAIT_CMD8,LISTEN_RESPONSE_CMD8,WAIT_RESPONSE_CMD8, // last 8
SEND_CMD55,WAIT_CMD55,LISTEN_RESPONSE_CMD55,WAIT_RESPONSE_CMD55, // last 12
SEND_ACMD41,WAIT_ACMD41,LISTEN_RESPONSE_ACMD41,WAIT_RESPONSE_ACMD41,ACMD41_DELAY, //17
SEND_CMD2,WAIT_CMD2,LISTEN_RESPONSE_CMD2,WAIT_RESPONSE_CMD2, //21
SEND_CMD3,WAIT_CMD3,LISTEN_RESPONSE_CMD3,WAIT_RESPONSE_CMD3, //25
SEND_CMD7,WAIT_CMD7,LISTEN_RESPONSE_CMD7,WAIT_RESPONSE_CMD7, //29
SEND2_CMD55,WAIT2_CMD55,LISTEN_RESPONSE2_CMD55,WAIT_RESPONSE2_CMD55,
SEND_ACMD6,WAIT_ACMD6,LISTEN_RESPONSE_ACMD6,WAIT_RESPONSE_ACMD6,
READY_TO_TRANSMIT,
DELAY_CLOCK_SWITCH,
TRANSMIT,WAIT_TRANSMIT,WAIT_END,FINISH_TRANSMIT,
TRANSMIT2,WAIT_TRANSMIT2,WAIT_END2,FINISH_TRANSMIT2,
WAIT_FOR_BUFFER
} state, next_state;
logic fast_clk_enable;
assign clk = fast_clk_enable ? fast_clk : slow_clk;
logic [$clog2(4114):0] counter;
logic sd_buffer_half;
logic [31:0] address;
(* MARK_DEBUG = "TRUE" *)
logic send_command_start;//, send_command_start_fast;
(* MARK_DEBUG = "TRUE" *)
logic [5:0] cmd;
logic [31:0] arg;
(* MARK_DEBUG = "TRUE" *)
wire send_command_ready;//, send_command_ready_fast;
(* MARK_DEBUG = "TRUE" *)
logic stored_sd_cmd;
// This needs to swap speeds
send_command slowSender(
clk,
crc_clk,
reset,
send_command_start,
cmd,
arg,
send_command_ready,
sd_cmd);
//send_command fastSender(
// fast_clk,
// crc_clk,
// reset,
// send_command_start_fast,
// cmd,
// arg,
// send_command_ready_fast,
// sd_cmd);
(* MARK_DEBUG = "TRUE" *)
logic read_command_listen;
logic [2:0] response_type;
(* MARK_DEBUG = "TRUE" *)
wire read_command_received;
wire [135:0] out_data;
// Hopefully this doesn't get optimized out...
(* MARK_DEBUG = "TRUE" *)
logic [47:0] received_data;
assign received_data = out_data[47:0];
// This doesn't need to swap speeds as we ignore responses to CMD17
read_command slowReader(
slow_clk,
reset,
read_command_listen,
response_type,
sd_cmd,
read_command_received,
out_data
);
// The data line is only ever used at fast_clk speeds
read_data dataHandler(
fast_clk,
reset,
sd_data,
buffer
);
//always_ff @(posedge crc_clk) begin
// stored_sd_cmd <= sd_cmd;
//end
// Next state logic
always_comb begin
case (state)
INIT: next_state=WAIT;
WAIT:
if (counter==0)
next_state=SEND_CMD0;
else
next_state=WAIT;
SEND_CMD0:
next_state=WAIT_CMD0;
WAIT_CMD0:
if (send_command_ready)
next_state=DELAY_CMD0;
else
next_state=WAIT_CMD0;
DELAY_CMD0:
if (counter == 0)
next_state=SEND_CMD8;
else
next_state=DELAY_CMD0;
// These state transitions are all very similar, this probably could
// be a macro
SEND_CMD8:
next_state=WAIT_CMD8;
WAIT_CMD8:
if (send_command_ready)
next_state=LISTEN_RESPONSE_CMD8;
else
next_state=WAIT_CMD8;
LISTEN_RESPONSE_CMD8:
next_state=WAIT_RESPONSE_CMD8;
WAIT_RESPONSE_CMD8:
if (read_command_received)
next_state=SEND_CMD55;
else
next_state=WAIT_RESPONSE_CMD8;
SEND_CMD2:
next_state=WAIT_CMD2;
WAIT_CMD2:
if (send_command_ready)
next_state=LISTEN_RESPONSE_CMD2;
else
next_state=WAIT_CMD2;
LISTEN_RESPONSE_CMD2:
next_state=WAIT_RESPONSE_CMD2;
WAIT_RESPONSE_CMD2:
if (read_command_received)
next_state=SEND_CMD3;
else
next_state=WAIT_RESPONSE_CMD2;
SEND_CMD3:
next_state=WAIT_CMD3;
WAIT_CMD3:
if (send_command_ready)
next_state=LISTEN_RESPONSE_CMD3;
else
next_state=WAIT_CMD3;
LISTEN_RESPONSE_CMD3:
next_state=WAIT_RESPONSE_CMD3;
WAIT_RESPONSE_CMD3:
if (read_command_received)
next_state=SEND_CMD7;
else
next_state=WAIT_RESPONSE_CMD3;
SEND_CMD7:
next_state=WAIT_CMD7;
WAIT_CMD7:
if (send_command_ready)
next_state=LISTEN_RESPONSE_CMD7;
else
next_state=WAIT_CMD7;
LISTEN_RESPONSE_CMD7:
next_state=WAIT_RESPONSE_CMD7;
WAIT_RESPONSE_CMD7:
if (read_command_received)
next_state=SEND2_CMD55;
else
next_state=WAIT_RESPONSE_CMD7;
SEND_CMD55:
next_state=WAIT_CMD55;
WAIT_CMD55:
if (send_command_ready)
next_state=LISTEN_RESPONSE_CMD55;
else
next_state=WAIT_CMD55;
LISTEN_RESPONSE_CMD55:
next_state=WAIT_RESPONSE_CMD55;
WAIT_RESPONSE_CMD55:
if (read_command_received)
next_state=SEND_ACMD41;
else
next_state=WAIT_RESPONSE_CMD55;
SEND_ACMD41:
next_state=WAIT_ACMD41;
WAIT_ACMD41:
if (send_command_ready)
next_state=LISTEN_RESPONSE_ACMD41;
else
next_state=WAIT_ACMD41;
LISTEN_RESPONSE_ACMD41:
next_state=WAIT_RESPONSE_ACMD41;
WAIT_RESPONSE_ACMD41:
if (read_command_received && !out_data[39])
next_state=ACMD41_DELAY;
else if (read_command_received && out_data[39])
next_state=SEND_CMD2;
else
next_state=WAIT_RESPONSE_ACMD41;
ACMD41_DELAY:
if (counter == 0)
next_state=SEND_CMD55;
else
next_state=ACMD41_DELAY;
SEND2_CMD55:
next_state=WAIT2_CMD55;
WAIT2_CMD55:
if (send_command_ready)
next_state=LISTEN_RESPONSE2_CMD55;
else
next_state=WAIT2_CMD55;
LISTEN_RESPONSE2_CMD55:
next_state=WAIT_RESPONSE2_CMD55;
WAIT_RESPONSE2_CMD55:
if (read_command_received)
next_state=SEND_ACMD6;
else
next_state=WAIT_RESPONSE2_CMD55;
SEND_ACMD6:
next_state=WAIT_ACMD6;
WAIT_ACMD6:
if (send_command_ready)
next_state=LISTEN_RESPONSE_ACMD6;
else
next_state=WAIT_ACMD6;
LISTEN_RESPONSE_ACMD6:
next_state=WAIT_RESPONSE_ACMD6;
WAIT_RESPONSE_ACMD6:
if (read_command_received)
next_state=READY_TO_TRANSMIT;
else
next_state=WAIT_RESPONSE_ACMD6;
READY_TO_TRANSMIT:
next_state=DELAY_CLOCK_SWITCH;
DELAY_CLOCK_SWITCH:
if (counter == 0)
next_state = TRANSMIT;
else
next_state = DELAY_CLOCK_SWITCH;
TRANSMIT:
next_state=WAIT_TRANSMIT;
WAIT_TRANSMIT:
if (sd_data==0)
next_state=WAIT_END;
else
next_state=WAIT_TRANSMIT;
WAIT_END:
if (counter==0)
next_state=FINISH_TRANSMIT;
else
next_state=WAIT_END;
FINISH_TRANSMIT:
next_state=TRANSMIT2;
TRANSMIT2:
next_state=WAIT_TRANSMIT2;
WAIT_TRANSMIT2:
if (sd_data==0)
next_state=WAIT_END2;
else
next_state=WAIT_TRANSMIT2;
WAIT_END2:
if (counter==0)
next_state=FINISH_TRANSMIT2;
else
next_state=WAIT_END2;
FINISH_TRANSMIT2:
next_state=WAIT_FOR_BUFFER;
WAIT_FOR_BUFFER:
if (sd_buffer_half==buffer.address_half)
next_state=WAIT_FOR_BUFFER;
else
next_state=TRANSMIT;
default:
next_state=INIT;
endcase
end
// This could mostly swap speeds ... however detecting the data line going low
// would not work
always_ff @(posedge clk) begin
stored_sd_cmd <= sd_cmd;
// Transition states
if (reset)
state <= INIT;
else
state <= next_state;
// Sequential outputs/logic
case (state)
INIT: begin
counter <= 80;
fast_clk_enable <= 0;
end
SEND_CMD0: begin
cmd<=0;
arg<=0;
send_command_start <= 1;
end
WAIT_CMD0: begin
counter<=20;
send_command_start<=0;
end
SEND_CMD8: begin
cmd <=8;
arg <='h1AA;
send_command_start <=1;
end
LISTEN_RESPONSE_CMD8: begin
response_type <= 7;
read_command_listen <= 1;
end
SEND_CMD55: begin
cmd <= 55;
arg <= 0;
send_command_start <=1;
end
SEND2_CMD55: begin
cmd <= 55;
// the arg should be the same as the preceding command
// and otherwise we'll lose the RCA bits
//arg <= 0;
send_command_start <=1;
end
LISTEN_RESPONSE_CMD55,LISTEN_RESPONSE2_CMD55: begin
response_type <= 1;
read_command_listen <= 1;
end
SEND_ACMD41: begin
cmd <= 41;
arg <= 'h40100000;
send_command_start <=1;
end
LISTEN_RESPONSE_ACMD41: begin
response_type <= 3;
read_command_listen <= 1;
end
WAIT_RESPONSE_ACMD41: begin
read_command_listen<=0;
counter<=100;
end
SEND_ACMD6: begin
cmd <= 6;
arg <= 'b10;
send_command_start <=1;
end
LISTEN_RESPONSE_ACMD6: begin
response_type <= 1;
read_command_listen <= 1;
end
SEND_CMD2: begin
cmd <= 2;
arg <= 0;
send_command_start <=1;
end
LISTEN_RESPONSE_CMD2: begin
response_type <= 2;
read_command_listen <= 1;
end
SEND_CMD3: begin
cmd <= 3;
arg <= 0;
send_command_start <=1;
end
LISTEN_RESPONSE_CMD3: begin
response_type <= 6;
read_command_listen <= 1;
end
SEND_CMD7: begin
cmd <= 7;
arg <= {out_data[39:24],16'h0000};
send_command_start <=1;
end
LISTEN_RESPONSE_CMD7: begin
response_type <= 1;
read_command_listen <= 1;
end
READY_TO_TRANSMIT: begin
fast_clk_enable <= 1;
address <= 0;
sd_buffer_half <= 0;
// Wait to actually transmit until clock is running and stable
counter <= 100;
end
TRANSMIT, TRANSMIT2: begin
cmd <= 17;
arg <= address;
send_command_start <= 1;
end
WAIT_TRANSMIT, WAIT_TRANSMIT2: begin
send_command_start <= 0;
counter <= 512*2+16+1;
end
FINISH_TRANSMIT:
address <= address +1;
FINISH_TRANSMIT2: begin
address <= address +1;
sd_buffer_half <= ~sd_buffer_half;
end
WAIT_FOR_BUFFER:
ready <= 1;
// The logic is simple enough in these to group them
WAIT, DELAY_CMD0, ACMD41_DELAY, WAIT_END, WAIT_END2, DELAY_CLOCK_SWITCH:
counter <= counter - 1;
WAIT_CMD8,WAIT_CMD55,WAIT_ACMD41,WAIT2_CMD55,WAIT_ACMD6,WAIT_CMD2,WAIT_CMD3,WAIT_CMD7:
send_command_start<=0;
WAIT_RESPONSE_CMD8,WAIT_RESPONSE_CMD55,WAIT_RESPONSE2_CMD55,WAIT_RESPONSE_ACMD6,
WAIT_RESPONSE_CMD2,WAIT_RESPONSE_CMD3,WAIT_RESPONSE_CMD7:
read_command_listen<=0;
default: ;
endcase
end
endmodule