mirror of
https://github.com/MiSTer-devel/MacPlus_MiSTer.git
synced 2025-02-17 01:31:02 +00:00
251 lines
8.0 KiB
Systemverilog
251 lines
8.0 KiB
Systemverilog
// ============================================================================
|
|
//
|
|
// f2sdram_safe_terminator for MiSTer platform
|
|
//
|
|
// ============================================================================
|
|
// Copyright (c) 2021 bellwood420
|
|
//
|
|
// Background:
|
|
//
|
|
// Terminating a transaction of burst writing(/reading) in its midstream
|
|
// seems to cause an illegal state to f2sdram interface.
|
|
//
|
|
// Forced reset request that occurs when loading other core is inevitable.
|
|
//
|
|
// So if it happens exactly within the transaction period,
|
|
// unexpected issues with accessing to f2sdram interface will be caused
|
|
// in next loaded core.
|
|
//
|
|
// It seems that only way to reset broken f2sdram interface is to reset
|
|
// whole SDRAM Controller Subsystem from HPS via permodrst register
|
|
// in Reset Manager.
|
|
// But it cannot be done safely while Linux is running.
|
|
// It is usually done when cold or warm reset is issued in HPS.
|
|
//
|
|
// Main_MiSTer is issuing reset for FPGA <> HPS bridges
|
|
// via brgmodrst register in Reset Manager when loading rbf.
|
|
// But it has no effect on f2sdram interface.
|
|
// f2sdram interface seems to belong to SDRAM Controller Subsystem
|
|
// rather than FPGA-to-HPS bridge.
|
|
//
|
|
// Main_MiSTer is also trying to issuing reset for f2sdram ports
|
|
// via fpgaportrst register in SDRAM Controller Subsystem when loading rbf.
|
|
// But according to the Intel's document, fpgaportrst register can be
|
|
// used to stretch the port reset.
|
|
// It seems that it cannot be used to assert the port reset.
|
|
//
|
|
// According to the Intel's document, there seems to be a reset port on
|
|
// Avalon-MM slave interface, but it cannot be found in Qsys generated HDL.
|
|
//
|
|
// To conclude, the only thing FPGA can do is not to break the transaction.
|
|
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
//
|
|
// Purpose:
|
|
// To prevent the issue, this module completes ongoing transaction
|
|
// on behalf of user logic, when reset is asserted.
|
|
//
|
|
// Usage:
|
|
// Insert this module into the bus line between
|
|
// f2sdram (Avalon-MM slave) and user logic (Avalon-MM master).
|
|
//
|
|
// Notice:
|
|
// Asynchronous reset request is not supported.
|
|
// Please feed reset request synchronized to clock.
|
|
//
|
|
module f2sdram_safe_terminator #(
|
|
parameter DATA_WIDTH = 64,
|
|
parameter BURSTCOUNT_WIDTH = 8
|
|
) (
|
|
// clk should be the same as one provided to f2sdram port
|
|
// clk should not be stop when reset is asserted
|
|
input clk,
|
|
// rst_req_sync should be synchronized to clk
|
|
// Asynchronous reset request is not supported
|
|
input rst_req_sync,
|
|
|
|
// Master port: connecting to Alavon-MM slave(f2sdram)
|
|
input waitrequest_master,
|
|
output [BURSTCOUNT_WIDTH-1:0] burstcount_master,
|
|
output [ADDRESS_WITDH-1:0] address_master,
|
|
input [DATA_WIDTH-1:0] readdata_master,
|
|
input readdatavalid_master,
|
|
output read_master,
|
|
output [DATA_WIDTH-1:0] writedata_master,
|
|
output [BYTEENABLE_WIDTH-1:0] byteenable_master,
|
|
output write_master,
|
|
|
|
// Slave port: connecting to Alavon-MM master(user logic)
|
|
output waitrequest_slave,
|
|
input [BURSTCOUNT_WIDTH-1:0] burstcount_slave,
|
|
input [ADDRESS_WITDH-1:0] address_slave,
|
|
output [DATA_WIDTH-1:0] readdata_slave,
|
|
output readdatavalid_slave,
|
|
input read_slave,
|
|
input [DATA_WIDTH-1:0] writedata_slave,
|
|
input [BYTEENABLE_WIDTH-1:0] byteenable_slave,
|
|
input write_slave
|
|
);
|
|
|
|
localparam BYTEENABLE_WIDTH = DATA_WIDTH/8;
|
|
localparam ADDRESS_WITDH = 32-$clog2(BYTEENABLE_WIDTH);
|
|
|
|
/*
|
|
* Capture init reset deaseert
|
|
*/
|
|
reg init_reset_deasserted = 1'b0;
|
|
|
|
always_ff @(posedge clk) begin
|
|
if (!rst_req_sync) begin
|
|
init_reset_deasserted <= 1'b1;
|
|
end
|
|
end
|
|
|
|
/*
|
|
* Lock stage
|
|
*/
|
|
reg lock_stage = 1'b0;
|
|
|
|
always_ff @(posedge clk) begin
|
|
if (rst_req_sync) begin
|
|
// Reset assert
|
|
if (init_reset_deasserted) begin
|
|
lock_stage <= 1'b1;
|
|
end
|
|
end
|
|
else begin
|
|
// Reset deassert
|
|
lock_stage <= 1'b0;
|
|
end
|
|
end
|
|
|
|
/*
|
|
* Write burst transaction observer
|
|
*/
|
|
reg state_write = 1'b0;
|
|
wire next_state_write;
|
|
|
|
wire burst_write_start = !state_write && next_state_write;
|
|
wire valid_write_data = state_write && !waitrequest_master;
|
|
wire burst_write_end = state_write && (write_burstcounter == write_burstcount_latch - 1'd1);
|
|
wire valid_non_burst_write = !state_write && write_slave && (burstcount_slave == 1) && !waitrequest_master;
|
|
|
|
reg [BURSTCOUNT_WIDTH-1:0] write_burstcounter = 0;
|
|
reg [BURSTCOUNT_WIDTH-1:0] write_burstcount_latch = 0;
|
|
reg [ADDRESS_WITDH-1:0] write_address_latch = 0;
|
|
|
|
always_ff @(posedge clk) begin
|
|
state_write <= next_state_write;
|
|
|
|
if (burst_write_start) begin
|
|
write_burstcounter <= waitrequest_master ? 1'd0 : 1'd1;
|
|
write_burstcount_latch <= burstcount_slave;
|
|
write_address_latch <= address_slave;
|
|
end
|
|
else if (valid_write_data) begin
|
|
write_burstcounter <= write_burstcounter + 1'd1;
|
|
end
|
|
end
|
|
|
|
always_comb begin
|
|
if (!state_write) begin
|
|
if (valid_non_burst_write)
|
|
next_state_write = 1'b0;
|
|
else if (write_slave)
|
|
next_state_write = 1'b1;
|
|
else
|
|
next_state_write = 1'b0;
|
|
end
|
|
else begin
|
|
if (burst_write_end)
|
|
next_state_write = 1'b0;
|
|
else
|
|
next_state_write = 1'b1;
|
|
end
|
|
end
|
|
|
|
reg [BURSTCOUNT_WIDTH-1:0] write_terminate_counter = 0;
|
|
reg [BURSTCOUNT_WIDTH-1:0] burstcount_latch = 0;
|
|
reg [ADDRESS_WITDH-1:0] address_latch = 0;
|
|
|
|
reg terminating = 0;
|
|
reg read_terminating = 0;
|
|
reg write_terminating = 0;
|
|
|
|
wire on_write_transaction = state_write && next_state_write;
|
|
wire on_start_write_transaction = !state_write && next_state_write;
|
|
|
|
always_ff @(posedge clk) begin
|
|
if (rst_req_sync) begin
|
|
// Reset assert
|
|
if (init_reset_deasserted) begin
|
|
if (!lock_stage) begin
|
|
// Even not knowing reading is in progress or not,
|
|
// if it is in progress, it will finish at some point, and no need to do anything.
|
|
// Assume that reading is in progress when we are not on write transaction.
|
|
burstcount_latch <= burstcount_slave;
|
|
address_latch <= address_slave;
|
|
terminating <= 1;
|
|
|
|
if (on_write_transaction) begin
|
|
write_terminating <= 1;
|
|
burstcount_latch <= write_burstcount_latch;
|
|
address_latch <= write_address_latch;
|
|
write_terminate_counter <= waitrequest_master ? write_burstcounter : write_burstcounter + 1'd1;
|
|
end
|
|
else if (on_start_write_transaction) begin
|
|
if (!valid_non_burst_write) begin
|
|
write_terminating <= 1;
|
|
write_terminate_counter <= waitrequest_master ? 1'd0 : 1'd1;
|
|
end
|
|
end
|
|
else if (read_slave && waitrequest_master) begin
|
|
// Need to keep read signal, burstcount and address until waitrequest_master deasserted
|
|
read_terminating <= 1;
|
|
end
|
|
end
|
|
else if (!waitrequest_master) begin
|
|
read_terminating <= 0;
|
|
end
|
|
end
|
|
end
|
|
else begin
|
|
// Reset deassert
|
|
if (!write_terminating) terminating <= 0;
|
|
read_terminating <= 0;
|
|
end
|
|
|
|
if (write_terminating) begin
|
|
// Continue write transaction until the end
|
|
if (!waitrequest_master) write_terminate_counter <= write_terminate_counter + 1'd1;
|
|
if (write_terminate_counter == burstcount_latch - 1'd1) write_terminating <= 0;
|
|
end
|
|
end
|
|
|
|
/*
|
|
* Bus mux depending on the stage.
|
|
*/
|
|
always_comb begin
|
|
if (terminating) begin
|
|
burstcount_master = burstcount_latch;
|
|
address_master = address_latch;
|
|
read_master = read_terminating;
|
|
write_master = write_terminating;
|
|
byteenable_master = 0;
|
|
end
|
|
else begin
|
|
burstcount_master = burstcount_slave;
|
|
address_master = address_slave;
|
|
read_master = read_slave;
|
|
byteenable_master = byteenable_slave;
|
|
write_master = write_slave;
|
|
end
|
|
end
|
|
|
|
// Just passing master <-> slave
|
|
assign writedata_master = writedata_slave;
|
|
assign readdata_slave = readdata_master;
|
|
assign readdatavalid_slave = readdatavalid_master;
|
|
assign waitrequest_slave = waitrequest_master;
|
|
|
|
endmodule
|