/* * Memory interface for Minimigmac * * Wraps the SRAM chips on the Minimig board and provides * support for RAM and ROM access from the processor, * backbus access via SPI, and "DMA" sound and video buffers * * Notes about video: We use a simple req edge based asynchronous * protocol since we are running the main logic separately from * the video timing generator. On each handshake, the memory * interface will fetch the next 16 pixels. Video addresses are * calculated internally based on the buffer select VIA bit for * a fixed resolution of 512x342. * * Starting addresses of the video buffers depend on the model * and amount of RAM. We emulate a 2Mb configuration but for * completeness, here are the address for all supported * configurations: * * System Main Screen Alternate * * Macintosh Plus, 1Mb $FA700 $F2700 * Macintosh Plus, 2Mb $1FA700 $1F2700 * Macintosh Plus, 2.5Mb $27A700 $272700 * Macintosh Plus, 4Mb $3FA700 $3F2700 * * The above addresses look confusing but basically it's really * just hard wired chip select I beleive on the real mac tho for * now we just use a full blown latch to hold the address since * we have silicon to spare :-) * * The total size of the video buffer is 0x5580 bytes * * For audio, we have: * * System Main Sound Alternate * Macintosh Plus, 1Mb $FFD00 $FA100 * Macintosh Plus, 2Mb $1FFD00 $1FA100 * Macintosh Plus, 2.5Mb $27FD00 $27A100 * Macintosh Plus, 4Mb $3FFD00 $3FA100 * * Implementation note: * * This is sub-optimal. We have a 3 cycle round trip, which could * probably go down to one or two (we have a mandatory idle cycle * between 2-cycle accesses). Works for now and is easy tho. * * The memory doesn't seem to require any hold times, on synthetized * cycles (video, SPI, ...) we establish everything at once and leave * the signals "on" for 2 cycles. On CPU initiated cycles, some * signals are established later, see comments in the CPU interface * module, but overall it fits the timings. The idle cycle should give * us the necessary resting time to avoid cases of both the memory and * the FPGA trying to drive the data bus. */ module mem_intf(input sysclk, input reset, /* SRAM pins */ inout [15:0] ram_data, output [19:1] ram_address, output [3:0] _ram_ce, output _ram_bhe, output _ram_ble, output _ram_we, output _ram_oe, /* Bus interface */ input bus_cs_ram, input bus_cs_rom, input bus_we, output bus_ack, input bus_ube, input bus_lbe, input bus_phase, input [15:0] bus_wdata, output [15:0] bus_rdata, input [22:1] bus_addr, /* Backbus interface */ input [5:0] bb_addr, input [7:0] bb_wdata, output [7:0] bb_rdata, input bb_strobe, input bb_wr, /* Asynchronous video interface */ input vid_bufsel, input vid_req, output reg vid_ack, output reg [15:0] vid_pixels /* TODO: Audio */ ); /* Owner of interface */ wire own_bus; wire own_vid; wire own_spi; /* Cooked bus address (ram/rom selection) */ wire [21:1] cooked_address; /* Latch of cs_rom vs. cs_ram */ reg rom_select; /* SPI byte write latch */ reg [21:0] spi_addr; reg [7:0] spi_data; reg spi_pending; reg spi_we; /* SPI decode signals */ wire spi_wreg; wire spi_reg_addr0; wire spi_reg_addr1; wire spi_reg_addr2; wire spi_reg_data; /* Muxed/cooked RAM control signals */ wire [15:0] ram_data_out; wire [21:1] ram_addr; /* Main memory enable */ wire ram_en; /* Video address generator. Buffer select bit is * inserted in there at bit 15. We cound down tho * eventually I may replace that with a reset input * from the video circuitry */ parameter vid_buf_base = 'h1F2700 / 2; parameter vid_buf_size = 'h2ac0; reg [21:1] vid_addr; reg [13:0] vid_words; wire vid_latch_pix; /* vid_req/vid_ack logic */ reg [2:0] vid_req_sync; reg vid_req_pending; wire vid_ack_reset; /* State machine */ localparam state_idle = 8'h10; /* idle, ram off, owner is bus */ localparam state_bus0 = 8'h11; /* bus access phase 0 */ localparam state_bus1 = 8'h13; /* bus access phase 1 */ localparam state_spi0 = 8'h21; /* spi access phase 0 */ localparam state_spi1 = 8'h23; /* spi access phase 1 */ localparam state_vid0 = 8'h41; localparam state_vid1 = 8'h47; /* State bits breakout: * * 7: * 6: own bit video * 5: own bit spi * 4: own bit bus * 3: * 2: inc address (vid/audio) * 1: bus phase * 0: ram active */ reg [7:0] state; /* Own lines off the state machine */ assign own_bus = state[4]; assign own_spi = state[5]; assign own_vid = state[6]; /* First setup ram_data_out, _ram_we and ram_addr based * on owner. ram_data_out is then applied to ram_data or * not based on _ram_we. * * XX. Remove the "default" case, make it SPI */ assign ram_data_out = own_bus ? bus_wdata : own_spi ? { spi_data, spi_data } : 16'b1; assign ram_en = state[0]; assign _ram_we = (own_bus ? (bus_cs_rom | ~bus_we) : own_spi ? ~spi_we : 1'b1) | ~ram_en; assign _ram_bhe = own_bus ? ~bus_ube : own_spi ? spi_addr[0] : own_vid ? 1'b0 : 1'b1; assign _ram_ble = own_bus ? ~bus_lbe : own_spi ? ~spi_addr[0] : own_vid ? 1'b0 : 1'b1; /* Cook RAM address based on RAM/ROM selection * We have 2M of RAM and 128k of ROM repeated */ assign cooked_address = rom_select ? { 5'b10000, bus_addr[16:1] } : { 1'b0, bus_addr[20:1] }; /* Mux appropriate source of RAM address */ assign ram_addr = own_bus ? cooked_address : own_spi ? spi_addr[21:1] : own_vid ? vid_addr : 0; /* Do ram_data input based on _ram_we */ assign ram_data = _ram_we ? 16'bz : ram_data_out; /* And set ram output enable accordingly */ assign _ram_oe = ~_ram_we | ~ram_en; /* Split ram_addr into banks */ assign ram_address = ram_addr[19:1]; assign _ram_ce[0] = ~(ram_addr[21:20] == 2'b00) | ~state[0]; assign _ram_ce[1] = ~(ram_addr[21:20] == 2'b01) | ~state[0]; assign _ram_ce[2] = ~(ram_addr[21:20] == 2'b10) | ~state[0]; assign _ram_ce[3] = ~(ram_addr[21:20] == 2'b11) | ~state[0]; /* RAM to bus data */ assign bus_rdata = own_bus ? ram_data : 16'b1; /* Ack for bus request in phase 0. WARNING: Must reproduce * the state machine logic in state_idle */ assign bus_ack = state == state_bus0; /* Latch rom_select on cs transitions */ always@(posedge sysclk or posedge reset) begin if (reset) rom_select <= 0; else begin if (bus_cs_ram) rom_select <= 0; else if (bus_cs_rom) rom_select <= 1; end end /* State machine */ always@(posedge sysclk or posedge reset) begin if (reset) state <= state_idle; else case(state) state_idle: begin /* Video request */ if (vid_req_pending) state <= state_vid0; /* SPI request */ else if (spi_pending) state <= state_spi0; /* CPU request */ else if (bus_cs_ram || bus_cs_rom) state <= state_bus0; end state_bus0: begin /* There should be no possible abort here * since we have ack out */ state <= state_bus1; end state_bus1: state <= state_idle; state_spi0: state <= state_spi1; state_spi1: state <= state_idle; state_vid0: state <= state_vid1; state_vid1: state <= state_idle; endcase end /* vid_req input synchronizer and edge detect */ always@(posedge sysclk or posedge reset) begin if (reset) begin vid_req_sync <= 3'b000; vid_req_pending <= 0; end else begin vid_req_sync[2] <= vid_req_sync[1]; vid_req_sync[1] <= vid_req_sync[0]; vid_req_sync[0] <= vid_req; if (vid_req_sync[2] == 0 && vid_req_sync[1]) vid_req_pending <= 1; else if (own_vid) vid_req_pending <= 0; end end assign vid_ack_reset = reset | ~vid_req_sync[1]; assign vid_latch_pix = state == state_vid0; /* vid_ack generation, synchronous set, asynchronous reset */ always@(posedge sysclk or posedge vid_ack_reset) begin if (vid_ack_reset) vid_ack <= 1'b0; else if (vid_latch_pix) vid_ack <= 1'b1; end /* vid address calculation */ always@(posedge sysclk or posedge reset) begin if (reset) begin vid_addr <= vid_buf_base; vid_addr[15] <= vid_bufsel; vid_words <= vid_buf_size-1; end else begin if (state[2]) begin if (vid_words == 0) begin vid_addr <= vid_buf_base; vid_addr[15] <= vid_bufsel; vid_words <= vid_buf_size-1; end else begin vid_addr <= vid_addr + 1; vid_words <= vid_words - 1; end end end end /* latch pixels */ always@(posedge sysclk or posedge reset) begin if (reset) begin vid_pixels <= 0; end else begin if (vid_latch_pix) begin vid_pixels <= ram_data; end end end /* SPI register interface */ `define MEMINTF_REG_ADDR0 0 `define MEMINTF_REG_ADDR1 1 `define MEMINTF_REG_ADDR2 2 `define MEMINTF_REG_DATA 3 assign spi_wreg = bb_strobe && bb_wr; assign spi_reg_addr0 = bb_addr[2:0] == `MEMINTF_REG_ADDR0; assign spi_reg_addr1 = bb_addr[2:0] == `MEMINTF_REG_ADDR1; assign spi_reg_addr2 = bb_addr[2:0] == `MEMINTF_REG_ADDR2; assign spi_reg_data = bb_addr[2:0] == `MEMINTF_REG_DATA; /* SPI control */ always@(posedge sysclk or posedge reset) begin if (reset) begin spi_pending <= 0; spi_we <= 0; end else begin if (bb_strobe && spi_reg_data) begin spi_pending <= 1; spi_we <= bb_wr; end else if (own_spi) begin spi_pending <= 0; end end end /* SPI data latch */ always@(posedge sysclk or posedge reset) begin if (reset) begin spi_data <= 0; end else begin if (spi_wreg && spi_reg_data) begin spi_data <= bb_wdata; end else if (own_spi && !spi_we && state[1]) begin spi_data <= spi_addr[0] ? ram_data[7:0] : ram_data[15:8]; end end end /* SPI address latch write */ always@(posedge sysclk or posedge reset) begin if (reset) begin spi_addr <= 0; end else begin if (own_spi && state[1]) begin spi_addr <= spi_addr + 1; end else if (spi_wreg) begin if (spi_reg_addr0) spi_addr[21:16] <= bb_wdata[5:0]; else if (spi_reg_addr1) spi_addr[15:8] <= bb_wdata; else if (spi_reg_addr2) spi_addr[7:0] <= bb_wdata; end end end /* SPI backbus register read logic */ assign bb_rdata = spi_reg_addr0 ? { 2'b0, spi_addr[21:16] } : spi_reg_addr1 ? spi_addr[15:8] : spi_reg_addr2 ? spi_addr[7:0] : spi_reg_data ? spi_data : 0; endmodule