SummerCart64/fw/rtl/memory/memory_flash.sv
Mateusz Faderewski 4eaa0b3353 [SC64][FW] Flash: fixed partial page write + handle data mask during write
Flash memory module had an issue when ending address was not page (256 bytes) aligned.

Now it's possible to write single bytes to the Flash instead of being forced to do 16 bit aligned writes.
2024-05-03 17:49:44 +02:00

501 lines
16 KiB
Systemverilog

interface flash_scb ();
logic erase_pending;
logic erase_done;
logic [7:0] erase_block;
modport controller (
output erase_pending,
input erase_done,
output erase_block
);
modport flash (
input erase_pending,
output erase_done,
input erase_block
);
endinterface
module flash_qspi (
input clk,
input reset,
input start,
input finish,
output logic busy,
output logic valid,
input output_enable,
input quad_enable,
output logic [7:0] rdata,
input [7:0] wdata,
output logic flash_clk,
output logic flash_cs,
inout [3:0] flash_dq
);
logic flash_dq_oe_s;
logic flash_dq_oe_q;
logic [3:0] flash_dq_out;
assign flash_dq[0] = flash_dq_oe_s ? flash_dq_out[0] : 1'bZ;
assign flash_dq[3:1] = flash_dq_oe_q ? flash_dq_out[3:1] : 3'bZZZ;
logic ff_clk;
logic ff_cs;
logic ff_dq_oe_s;
logic ff_dq_oe_q;
logic [3:0] ff_dq_out;
logic [3:0] ff_dq_in;
always_ff @(posedge clk) begin
flash_clk <= ff_clk;
flash_cs <= ff_cs;
flash_dq_oe_s <= ff_dq_oe_s;
flash_dq_oe_q <= ff_dq_oe_q;
flash_dq_out <= ff_dq_out;
ff_dq_in <= flash_dq;
end
logic running;
logic exit;
logic valid_enable;
logic quad_mode;
logic [2:0] counter;
logic [7:0] output_shift;
logic [2:0] sample_s;
logic [2:0] sample_q;
logic [2:0] valid_ff;
assign ff_dq_out = quad_mode ? output_shift[7:4] : {3'bXXX, output_shift[7]};
always_ff @(posedge clk) begin
sample_s <= {sample_s[1:0], 1'b0};
sample_q <= {sample_q[1:0], 1'b0};
valid_ff <= {valid_ff[1:0], 1'b0};
if (reset) begin
ff_clk <= 1'b0;
ff_cs <= 1'b1;
ff_dq_oe_s <= 1'b0;
ff_dq_oe_q <= 1'b0;
busy <= 1'b0;
running <= 1'b0;
end else begin
if (running) begin
ff_clk <= ~ff_clk;
if (!ff_clk) begin
if (counter == 3'd0) begin
busy <= 1'b0;
valid_ff[0] <= valid_enable;
end
if (!quad_mode) begin
sample_s[0] <= 1'b1;
end else begin
sample_q[0] <= 1'b1;
end
end else begin
counter <= counter - 1'd1;
if (counter == 3'd0) begin
running <= 1'b0;
end
if (!quad_mode) begin
output_shift <= {output_shift[6:0], 1'bX};
end else begin
output_shift <= {output_shift[3:0], 4'bXXXX};
end
end
end
if (exit) begin
ff_cs <= 1'b1;
counter <= counter - 1'd1;
if (counter == 3'd0) begin
busy <= 1'b0;
exit <= 1'b0;
end
end
if (!busy) begin
if (start) begin
ff_clk <= 1'b0;
ff_cs <= 1'b0;
ff_dq_oe_s <= !quad_enable || (quad_enable && output_enable);
ff_dq_oe_q <= quad_enable && output_enable;
busy <= 1'b1;
running <= 1'b1;
valid_enable <= !output_enable;
quad_mode <= quad_enable;
counter <= quad_enable ? 3'd1 : 3'd7;
output_shift <= wdata;
end else if (finish) begin
busy <= 1'b1;
exit <= 1'b1;
counter <= wdata[2:0];
end
end
end
end
always_ff @(posedge clk) begin
valid <= 1'b0;
if (sample_s[2]) begin
rdata <= {rdata[6:0], ff_dq_in[1]};
end
if (sample_q[2]) begin
rdata <= {rdata[3:0], ff_dq_in};
end
if (valid_ff[2]) begin
valid <= 1'b1;
end
end
endmodule
module memory_flash (
input clk,
input reset,
flash_scb.flash flash_scb,
mem_bus.memory mem_bus,
output flash_clk,
output flash_cs,
inout [3:0] flash_dq
);
logic start;
logic finish;
logic busy;
logic valid;
logic output_enable;
logic quad_enable;
logic [7:0] rdata;
logic [7:0] wdata;
flash_qspi flash_qspi_inst (
.clk(clk),
.reset(reset),
.start(start),
.finish(finish),
.busy(busy),
.valid(valid),
.output_enable(output_enable),
.quad_enable(quad_enable),
.rdata(rdata),
.wdata(wdata),
.flash_clk(flash_clk),
.flash_cs(flash_cs),
.flash_dq(flash_dq)
);
typedef enum bit [7:0] {
FLASH_CMD_PAGE_PROGRAM = 8'h02,
FLASH_CMD_READ_STATUS_1 = 8'h05,
FLASH_CMD_WRITE_ENABLE = 8'h06,
FLASH_CMD_BLOCK_ERASE_64KB = 8'hD8,
FLASH_CMD_FAST_READ_QUAD_IO = 8'hEB
} e_flash_cmd;
typedef enum {
FLASH_STATUS_1_BUSY = 0
} e_flash_status_1;
typedef enum bit [3:0] {
STATE_IDLE,
STATE_WRITE_ENABLE,
STATE_ERASE,
STATE_PROGRAM_START,
STATE_PROGRAM,
STATE_PROGRAM_END,
STATE_WAIT,
STATE_READ_START,
STATE_READ,
STATE_READ_END
} e_state;
e_state state;
logic [2:0] counter;
logic valid_counter;
logic [23:0] current_address;
always_ff @(posedge clk) begin
start <= 1'b0;
finish <= 1'b0;
flash_scb.erase_done <= 1'b0;
mem_bus.ack <= 1'b0;
if (reset) begin
state <= STATE_IDLE;
end else begin
if ((start || finish) && !busy) begin
counter <= counter + 1'd1;
end
case (state)
STATE_IDLE: begin
output_enable <= 1'b1;
quad_enable <= 1'b0;
counter <= 3'd0;
if (flash_scb.erase_pending) begin
state <= STATE_WRITE_ENABLE;
end else if (mem_bus.request) begin
current_address <= {mem_bus.address[23:1], 1'b0};
if (mem_bus.write) begin
current_address[0] <= (~mem_bus.wmask[1]);
state <= STATE_WRITE_ENABLE;
end else begin
state <= STATE_READ_START;
end
end
end
STATE_WRITE_ENABLE: begin
case (counter)
3'd0: begin
start <= 1'b1;
wdata <= FLASH_CMD_WRITE_ENABLE;
end
3'd1: begin
finish <= 1'b1;
wdata <= 8'd5;
if (!busy) begin
counter <= 3'd0;
if (flash_scb.erase_pending) begin
state <= STATE_ERASE;
end else begin
state <= STATE_PROGRAM_START;
end
end
end
endcase
end
STATE_ERASE: begin
case (counter)
3'd0: begin
start <= 1'b1;
wdata <= FLASH_CMD_BLOCK_ERASE_64KB;
end
3'd1: begin
start <= 1'b1;
wdata <= flash_scb.erase_block;
end
3'd2: begin
start <= 1'b1;
wdata <= 8'd0;
end
3'd3: begin
start <= 1'b1;
wdata <= 8'd0;
end
3'd4: begin
finish <= 1'b1;
wdata <= 8'd5;
if (!busy) begin
flash_scb.erase_done <= 1'b1;
counter <= 3'd0;
state <= STATE_WAIT;
end
end
endcase
end
STATE_PROGRAM_START: begin
case (counter)
3'd0: begin
start <= 1'b1;
wdata <= FLASH_CMD_PAGE_PROGRAM;
end
3'd1: begin
start <= 1'b1;
wdata <= current_address[23:16];
end
3'd2: begin
start <= 1'b1;
wdata <= current_address[15:8];
end
3'd3: begin
start <= 1'b1;
wdata <= current_address[7:0];
if (!busy) begin
counter <= 3'd0 + current_address[0];
state <= STATE_PROGRAM;
end
end
endcase
end
STATE_PROGRAM: begin
case (counter)
3'd0: begin
start <= 1'b1;
wdata <= mem_bus.wdata[15:8];
if (start && !busy) begin
current_address <= current_address + 1'd1;
if (!mem_bus.wmask[0]) begin
counter <= 3'd2;
mem_bus.ack <= 1'b1;
end
end
end
3'd1: begin
start <= 1'b1;
wdata <= mem_bus.wdata[7:0];
if (!busy) begin
mem_bus.ack <= 1'b1;
current_address <= current_address + 1'd1;
end
end
3'd2: begin
if (current_address[7:0] == 8'h00) begin
state <= STATE_PROGRAM_END;
end else if (mem_bus.request && !mem_bus.ack) begin
if (mem_bus.write && mem_bus.wmask[1] && (mem_bus.address[23:0] == current_address)) begin
counter <= 3'd0;
end else begin
state <= STATE_PROGRAM_END;
end
end else if (!busy) begin
state <= STATE_PROGRAM_END;
end
end
endcase
end
STATE_PROGRAM_END: begin
finish <= 1'b1;
wdata <= 8'd5;
if (finish && !busy) begin
counter <= 3'd0;
state <= STATE_WAIT;
end
end
STATE_WAIT: begin
case (counter)
3'd0: begin
start <= 1'b1;
output_enable <= 1'b1;
wdata <= FLASH_CMD_READ_STATUS_1;
end
3'd1: begin
start <= 1'b1;
output_enable <= 1'b0;
end
3'd2: begin
finish <= 1'b1;
wdata <= 8'd0;
end
3'd3: begin
counter <= counter;
end
endcase
if (valid) begin
if (rdata[FLASH_STATUS_1_BUSY]) begin
counter <= 3'd0;
end else begin
state <= STATE_IDLE;
end
end
end
STATE_READ_START: begin
case (counter)
3'd0: begin
start <= 1'b1;
wdata <= FLASH_CMD_FAST_READ_QUAD_IO;
end
3'd1: begin
start <= 1'b1;
quad_enable <= 1'b1;
wdata <= current_address[23:16];
end
3'd2: begin
start <= 1'b1;
wdata <= current_address[15:8];
end
3'd3: begin
start <= 1'b1;
wdata <= current_address[7:0];
end
3'd4: begin
start <= 1'b1;
wdata <= 8'hFF;
end
3'd5: begin
start <= 1'b1;
end
3'd6: begin
start <= 1'b1;
if (!busy) begin
counter <= 3'd0;
valid_counter <= 1'b0;
state <= STATE_READ;
end
end
endcase
end
STATE_READ: begin
case (counter)
3'd0: begin
start <= 1'b1;
output_enable <= 1'b0;
end
3'd1: begin
start <= 1'b1;
end
3'd2: begin end
3'd3: begin
if (flash_scb.erase_pending) begin
state <= STATE_READ_END;
end else if (mem_bus.request && !mem_bus.ack) begin
if (mem_bus.write || (mem_bus.address[23:0] != current_address)) begin
state <= STATE_READ_END;
end else begin
start <= 1'b1;
counter <= 3'd0;
end
end
end
endcase
if (valid) begin
valid_counter <= ~valid_counter;
if (valid_counter) begin
mem_bus.ack <= 1'b1;
counter <= counter + 1'd1;
current_address <= current_address + 2'd2;
end
end
end
STATE_READ_END: begin
finish <= 1'b1;
wdata <= 8'd0;
if (!busy) begin
state <= STATE_IDLE;
end
end
default: begin
state <= STATE_IDLE;
end
endcase
end
end
always_ff @(posedge clk) begin
if (valid) begin
mem_bus.rdata <= {mem_bus.rdata[7:0], rdata};
end
end
endmodule