// MMC/SD controller for ABC80 // Designed to be compatible with the standard ABC-DOS controllers // // Note: in order for this to not require internal tristate buffers in // the FPGA, the data bus is split, and the "input" bus will be driven // to all FF when the card is not selected, so all inputs can be ANDed. // // The I/O port range is decoded as follows: // // A7..A6 - unused // A5 - SD card (1) / DMA engine (0) // A4..A0 - register select on CompactFlash card/DMA engine, see // respective unit module sdcontroller( reset_n, // Global reset clk, // CPU clk (25 MHz) sd_cs_n, // SD card CS# (CD, DAT3) sd_di, // SD card DI (MOSI, CMD) sd_clk, // SD card CLK (SCLK) sd_do, // SD card SO (MISO, DAT0) sd_cd_n, // SD socket CD# (Card Detect) switch sd_we_n, // SD socket WE# (Write Enable) switch abc_do, // ABC-bus data out (CPU->controller) abc_di, // ABC-bus data in (controller->CPU) abc_out_n, // ABC-bus data out select (OUT 0) abc_cs_n, // ABC-bus Card Select abc_c1_n, // ABC-bus Command 1 (OUT 2) abc_c2_n, // ABC-bus Command 2 (OUT 3) abc_c3_n, // ABC-bus Command 3 (OUT 4) abc_c4_n, // ABC-bus Command 4 (OUT 5) abc_inp_n, // ABC-bus data in select (IN 0) abc_status_n, // ABC-bus status (IN 1) abc_rst_n, // ABC-bus reset (IN 7) select, // Selected LED active, // Active LED errled // Error LED code ); input reset_n; input clk; output sd_cs_n; output sd_di; output sd_clk; input sd_do; input sd_cd_n; input sd_we_n; input [7:0] abc_do; output [7:0] abc_di; input abc_out_n; input abc_cs_n; input abc_c1_n; input abc_c2_n; input abc_c3_n; input abc_c4_n; input abc_inp_n; input abc_status_n; input abc_rst_n; output select; output active; output [7:0] errled; reg [7:0] errled = 8'h00; // Forward declaration wire cpu_wait_n; // If we should raise WAIT# to CPU // Which select code this device uses... select code 36 decimal is used // for hard disk controllers; this maps this device as HDx: parameter selectcode = 6'd36; reg selected; // ------------------------------------------------------------------------ // Reset and ABC-bus select // Note: for glitch prevention reasons, treat RST# and C3# as synchronous // resets only. // ------------------------------------------------------------------------ reg ireset; always @(posedge clk) begin if ( ~reset_n ) begin ireset <= 1'b1; selected <= 1'b0; end else // clock begin ireset <= 1'b0; if ( ~abc_rst_n ) begin ireset <= 1'b1; selected <= 1'b0; end else begin if ( selected & ~abc_c3_n ) ireset <= 1'b1; if ( ~abc_cs_n ) selected <= (abc_do[5:0] == selectcode); end end end // ------------------------------------------------------------------------ // Controller CPU // ------------------------------------------------------------------------ wire cpu_m1_n; wire cpu_iorq_n; wire cpu_mreq_n; wire cpu_rd_n; wire cpu_wr_n; wire [15:0] cpu_a; wire [7:0] cpu_di; wire [7:0] cpu_do; reg cpu_int_n; reg cpu_nmi_n; T80se sd_cpu ( .clk_n ( clk ), .reset_n ( ~ireset ), .clken ( cpu_wait_n ), .wait_n ( 1'b1 ), .int_n ( cpu_int_n ), .nmi_n ( cpu_nmi_n ), .busrq_n ( 1'b1 ), .m1_n ( cpu_m1_n ), .mreq_n ( cpu_mreq_n ), .iorq_n ( cpu_iorq_n ), .rd_n ( cpu_rd_n ), .wr_n ( cpu_wr_n ), .a ( cpu_a ), .di ( cpu_di ), .do ( cpu_do ) ); // C1# from the ABC bus generates interrupt; this is used to // reset the controller state machine to the command state. always @(posedge clk) begin if ( ireset ) cpu_int_n <= 1'b1; else if ( selected & ~abc_c1_n ) cpu_int_n <= 1'b0; else if ( ~cpu_m1_n & ~cpu_iorq_n ) // INTAK cpu_int_n <= 1'b1; end // always @ (posedge ireset or posedge clk) // Generate NMI if we do an I/O operation without A7 set, // and the card is not present. always @(posedge clk) begin if ( ireset ) cpu_nmi_n <= 1'b1; else if ( ~cpu_iorq_n ) cpu_nmi_n <= cpu_a[7] | ~sd_cd_n; end // ------------------------------------------------------------------------ // Memory -- second port of RAM used for "DMA" to the main CPU // We split the memory between ROM and RAM to closer mimic a "real" // microcontroller-based design here; consider it a prototyping feature. // RAM is selected by controller CPU A15. // ------------------------------------------------------------------------ wire [7:0] memrd_rom; // ROM output wire [7:0] memrd_ram; // RAM output wire [7:0] memrd; // Read data to controller CPU reg write_flag; // DMA if we should write data to controller RAM reg read_flag; // DMA if we just read data from controller RAM reg [7:0] cpudata_di; // DMA data to controller memory wire [7:0] cpudata_do; // DMA data from controller memory reg [10:0] cpudata_addr; // DMA target address sddrom sddrom ( .clock ( ~clk ), .address ( cpu_a[9:0] ), .q ( memrd_rom ) ); sddram sddram ( .clock ( ~clk ), .address_a ( cpu_a[10:0] ), .data_a ( cpu_do ), .wren_a ( ~cpu_mreq_n & ~cpu_wr_n & cpu_a[15] ), .q_a ( memrd_ram ), .address_b ( cpudata_addr ), .data_b ( cpudata_di ), .wren_b ( write_flag ), .q_b ( cpudata_do ) ); assign memrd = cpu_a[15] ? memrd_ram : memrd_rom; // ------------------------------------------------------------------------ // ABC-bus interface (DMA engine) // // Control interface addressed on I/O with A5 = 0 // The following ports are decoded: // OUT 0x00: write main CPU status (returned to main CPU by IN 0) // OUT 0x01: write aux CPU status (returned to main CPU by IN 1) // OUT 0x02-0x03: set DMA byte counter, A0 is bit 8 of counter // OUT 0x04: bits [7:0] of DMA address // OUT 0x05/0x07: bits [10:8] of DMA address; A1 is direction bit // (0 = host->controller, 1 = controller->host) // OUT 0x06: write error LED code (bit 7 = on, 6:0 = 7-seg pattern) // IN 0x00: D0 = DMA active (transaction in progress) // D1 = direction bit (see above) // D2 = card present // D3 = card write protect // D4 = set by hard reset, cleared by OUT 0x00 // ------------------------------------------------------------------------ reg [8:0] cpudata_ctr; // Bytes left to DMA reg cpudata_dir; // 0 = out, 1 = inp reg [7:0] aux_status; // Auxilliary status reg [7:0] main_status; // Primary status wire [7:0] dma_status; // Controller CPU query DMA engine status wire dma_stat_sel = ~cpu_iorq_n & cpu_m1_n & ~cpu_a[5]; // IN 0x00 wire dma_active = |cpudata_ctr; reg [7:0] abc_di; reg hard_reset; always @(*) begin if ( selected & ~abc_inp_n ) if ( dma_active && cpudata_dir == 1 ) abc_di = cpudata_do; else abc_di = aux_status; else if ( selected & ~abc_status_n ) abc_di = main_status; else abc_di = 8'hFF; end // always @ (*) always @(posedge clk) begin if ( ireset ) begin cpudata_addr <= ~11'b0; cpudata_ctr <= 9'd0; cpudata_dir <= 1'b0; write_flag <= 1'b0; read_flag <= 1'b0; main_status <= 8'h00; aux_status <= 8'h00; hard_reset <= ~reset_n; end else begin write_flag <= 1'b0; read_flag <= 1'b0; if ( selected & ~abc_out_n ) begin if ( dma_active & ~cpudata_dir ) begin cpudata_di <= abc_do; write_flag <= 1'b1; end end // if ( selected & ~abc_out_n ) if ( selected & ~abc_inp_n ) begin if ( dma_active & cpudata_dir ) begin // abc_di is generated above read_flag <= 1'b1; end end // if ( selected & ~abc_inp_n ) if ( (write_flag & ~(selected & ~abc_out_n)) | (read_flag & ~(selected & ~abc_inp_n)) ) begin // We just completed an OUT or INP cycle cpudata_addr <= cpudata_addr + 1; cpudata_ctr <= cpudata_ctr - 1; // Turn off START COMMAND main_status[7] <= 0; // If this was the last byte, turn on busy if ( cpudata_ctr == 1 ) main_status[0] <= 0; end // Set counters or status based on commands from controller CPU if ( ~cpu_iorq_n & cpu_m1_n & ~cpu_wr_n & ~cpu_a[5] ) begin casex ( cpu_a[2:0] ) 3'b000: // OUT 0x00 begin main_status <= cpu_do; hard_reset <= 1'b0; end 3'b001: // OUT 0x01 aux_status <= cpu_do; 3'b01x: // OUT 0x02-0x03 cpudata_ctr <= { cpu_a[0], cpu_do }; 3'b100: // OUT 0x04 cpudata_addr[7:0] <= cpu_do; 3'b1x1: // OUT 0x05 (H2C), 0x07 (C2H) begin cpudata_addr[10:8] <= cpu_do[2:0]; cpudata_dir <= cpu_a[1]; end 3'b110: errled <= cpu_do; endcase // casex( cpu_a[2:0] ) end end // else: !if( ireset ) end // always @ (posedge ireset or posedge clk) // Bit 0: DMA still in progress // Bit 1: Direction of DMA // Bit 2: Card Present // Bit 3: Card Write Protected // Bit 4: Hard reset assign dma_status = { 3'b0, hard_reset, sd_we_n, ~sd_cd_n, cpudata_dir, dma_active }; // ------------------------------------------------------------------------ // SD card interface // // This drives the SD card in SPI mode. We support two speeds: // 12.5 MHz for normal operation, and 25 MHz/64 = 391 kHz for // initialization. // // This is addressed by I/O with A5 = 1, and exports the following I/O // ports, address bits can be combined: // // A0 write - loads the output shift register from the CPU // A1 write - set speed (D0 = low speed) and CS# (D1, 1 = not selected) // A2 - clear CRC registers // A3 - select CRC register input (0 = input, 1 = output) // A4 - start bus transaction // On read, A[1:0]: // 00 - read input latch // 01 - read CRC7 (in D[7:1], D0 = 1) // 10 - read CRC16[15:8] // 11 - read CRC16[7:0] // ------------------------------------------------------------------------ reg [7:0] sd_shr_out; reg [7:0] sd_shr_in; reg [2:0] sd_out_ctr; // Output bit counter reg sd_active; // Transfer in progress reg sd_active_neg; // Transfer in progress, first pos clock seen reg sd_cs_n_reg; // CS# output reg sd_slow; reg [5:0] sd_clk_ctr; // Counter for the clock reg sd_clk_out; wire sd_clk_pos; // SD clock positive strobe wire sd_clk_neg; // SD clock negative strobe reg sd_crcsrc; // CRC generator input reg sd_crcstb; // Strobe for CRC generator reg [6:0] sd_crc7; // CRC-7 generator reg [15:0] sd_crc16; // CRC-16 generator wire sd_sel = ~cpu_iorq_n & cpu_m1_n & cpu_a[5]; wire sd_cmd = sd_sel & ~sd_active; // CPU command we can act on reg sd_cmd_ok; // Valid CPU command received wire sd_start = sd_cmd & cpu_a[4]; // Transaction begin wire sd_data_out = sd_shr_out[7]; // Output pins - tristate if card not present assign sd_di = ~sd_cd_n ? sd_data_out : 1'bz; assign sd_clk = ~sd_cd_n ? sd_clk_out : 1'bz; assign sd_cs_n = ~sd_cd_n ? sd_cs_n_reg : 1'bz; // If we try an action while a bus transaction is in progress, // wait. The register sd_cmd_ok is used to prevent WAIT# from // being asserted when we already started a transaction on *this* // I/O operation. // // sd_sel: 0 0 0 0 1 1 1 0 0 0 0 0 0 1 1 1 1 1 0 0 0 // sd_active: 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 // sd_cmd: 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 // sd_cmd_ok: 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 1 1 0 0 // cpu_wait_n: 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 always @(posedge clk) if (ireset) sd_cmd_ok <= 1'b0; else sd_cmd_ok <= sd_sel & (~sd_active | sd_cmd_ok); assign cpu_wait_n = ~(sd_sel & sd_active) | sd_cmd_ok; // SD clock generator; this counter is used to generate the slow clock. always @(posedge clk) if (sd_start) sd_clk_ctr <= 0; else sd_clk_ctr <= sd_clk_ctr+1; // Generate strobes from the sd_clk_ctr; this is defined to be 1 // in the internal cycle before sd_clk goes positive/negative. wire ctr_pol = sd_slow ? sd_clk_ctr[5] : sd_clk_ctr[0]; wire ctr_val = ~sd_slow | &sd_clk_ctr[4:0]; assign sd_clk_pos = sd_active & ctr_val & ctr_pol; assign sd_clk_neg = sd_active_neg & ctr_val & ~ctr_pol; always @(posedge clk) if (ireset) sd_clk_out <= 1'b0; else sd_clk_out <= (sd_clk_out | sd_clk_pos) & ~sd_clk_neg; always @(posedge clk) if (ireset) begin sd_shr_out <= 8'hFF; sd_cs_n_reg <= 1'b1; sd_slow <= 1'b1; sd_active <= 1'b0; sd_active_neg <= 1'b0; sd_out_ctr <= 3'h0; sd_crcstb <= 1'b0; sd_shr_in <= 8'hxx; sd_crcsrc <= 1'bx; end else begin if (sd_clk_pos) begin sd_shr_in <= {sd_shr_in[6:0], sd_do}; sd_out_ctr <= sd_out_ctr + 1; sd_active_neg <= 1'b1; end if (sd_clk_neg) begin sd_shr_out <= {sd_shr_out[6:0], 1'b1}; sd_active <= |sd_out_ctr; sd_active_neg <= |sd_out_ctr; end sd_crcstb <= sd_clk_pos; // CRCs are computed one cycle after posedge if (sd_cmd) begin if (~cpu_wr_n & cpu_a[0]) sd_shr_out <= cpu_do; if (~cpu_wr_n & cpu_a[1]) {sd_cs_n_reg, sd_slow} <= cpu_do[1:0]; if (cpu_a[4]) begin sd_active <= 1'b1; sd_out_ctr <= 3'h0; // Should be the case already sd_crcsrc <= cpu_a[3]; end end // if (sd_cmd) end // CRC generators: we have one 7-bit and one 16-bit, shared between // input and output. The controller CPU has to specify where it wants // the input from by setting A3 properly when starting a bus // transaction (A4 = 1). // // The CRC generators run one cycle behind the positive sd_clk strobe. wire sd_crcbit = sd_crcsrc ? sd_data_out : sd_shr_in[0]; wire sd_crc7in = sd_crcbit ^ sd_crc7[6]; always @(posedge clk) if (sd_cmd & cpu_a[2]) sd_crc7 <= 7'h00; else if (sd_crcstb) sd_crc7 <= {sd_crc7[5:3], sd_crc7[2]^sd_crc7in, sd_crc7[1:0], sd_crc7in}; wire sd_crc16in = sd_crcbit ^ sd_crc16[15]; always @(posedge clk) if (sd_cmd & cpu_a[2]) sd_crc16 <= 16'h0000; else if (sd_crcstb) sd_crc16 <= {sd_crc16[14:12], sd_crc16[11]^sd_crc16in, sd_crc16[10:5], sd_crc16[4]^sd_crc16in, sd_crc16[3:0], sd_crc16in}; // Data out to controller CPU reg [7:0] sd_cpu_rd; always @(*) case (cpu_a[1:0]) 2'b00: sd_cpu_rd = sd_shr_in; 2'b01: sd_cpu_rd = {sd_crc7, 1'b1}; 2'b10: sd_cpu_rd = sd_crc16[15:8]; 2'b11: sd_cpu_rd = sd_crc16[7:0]; endcase // case(cpu_a[1:0]) // ------------------------------------------------------------------------ // Controller CPU data select // ------------------------------------------------------------------------ assign cpu_di = {8{cpu_rd_n}} | // Always FF unless we're reading something (({8{cpu_mreq_n}} | memrd) & ({8{~sd_sel}} | sd_cpu_rd) & ({8{~dma_stat_sel}} | dma_status)); // ------------------------------------------------------------------------ // Output LEDs // ------------------------------------------------------------------------ parameter active_time = 21; // 2^21 cycles @ 25 MHz ~ 84 ms reg [active_time:0] active_delay; assign select = selected; // For external LED, might need hysteresis always @(posedge clk) if ( ireset ) active_delay <= ~0; else if ( sd_active ) active_delay <= 0; else if ( ~active_delay[active_time] ) active_delay <= active_delay + 1; assign active = ~active_delay[active_time]; endmodule // cfcontroller