From fe943508069155effb1f2f57930da8ba2f721220 Mon Sep 17 00:00:00 2001 From: Gyorgy Szombathelyi Date: Sun, 9 Oct 2022 18:13:55 +0200 Subject: [PATCH] Update MiST modules --- common/mist/README.md | 4 +- common/mist/cdda_fifo.v | 107 ++++++++ common/mist/data_io.v | 324 ++++++++++++++++++++++-- common/mist/ide.v | 405 ++++++++++++++++++++++++++++++ common/mist/ide_fifo.v | 86 +++++++ common/mist/mist.qip | 4 + common/mist/mist.vhd | 3 +- common/mist/mist_video.v | 7 +- common/mist/scandoubler.v | 277 ++++++++++++--------- common/mist/sd_card.v | 3 + common/mist/user_io.v | 512 +++++++++++++++++++------------------- 11 files changed, 1335 insertions(+), 397 deletions(-) create mode 100644 common/mist/cdda_fifo.v create mode 100644 common/mist/ide.v create mode 100644 common/mist/ide_fifo.v diff --git a/common/mist/README.md b/common/mist/README.md index 588d54cd..99157a20 100644 --- a/common/mist/README.md +++ b/common/mist/README.md @@ -7,8 +7,10 @@ The modules: - user_io.v - communicating with the IO controller. - data_io.v - handling file uploads from the IO controller. - mist_video.v - a video pipeline, which gives an optional scandoubler, OSD and rgb2ypbpr conversion. -- osd.v, scandoubler.v, rgb2ypbpr.sv, cofi.sv - these are used in mist_video, but can be used separately, too. +- osd.v, scandoubler.v, rgb2ypbpr.v, cofi.sv - these are used in mist_video, but can be used separately, too. - sd_card.v - gives an SPI interface with SD-Card commands towards the IO-Controller, accessing .VHD and other mounted files. +- ide.v, ide_fifo.v - a bridge between a CPU and the data_io module for IDE/ATAPI disks. +- cdda_fifo.v - a module which connects data_io with a DAC for CDDA playback. - dac.vhd - a simple sigma-delta DAC for audio output. - arcade_inputs.v - mostly for arcade-style games, gives access to the joysticks with MAME-style keyboard mapping. - mist.vhd - VHDL component declarations for user_io and mist_video. diff --git a/common/mist/cdda_fifo.v b/common/mist/cdda_fifo.v new file mode 100644 index 00000000..d402930f --- /dev/null +++ b/common/mist/cdda_fifo.v @@ -0,0 +1,107 @@ +// +// cdda_fifo.v +// +// CDDA FIFO for the MiST board +// https://github.com/mist-devel +// +// Copyright (c) 2022 Gyorgy Szombathelyi +// +// This source file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This source file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +/////////////////////////////////////////////////////////////////////// + +module cdda_fifo +( + input clk_sys, + input clk_en, // set to 1 when using the stock data_io + input cen_44100, // 44100 HZ clock enable + input reset, + + // data_io interface + output hdd_cdda_req, + input hdd_cdda_wr, + input [15:0] hdd_data_out, + + // sample output + output reg [15:0] cdda_l, + output reg [15:0] cdda_r +); + +// 4k x 16bit default FIFO size +parameter FIFO_DEPTH = 12; +reg [15:0] fifo[2**FIFO_DEPTH]; +reg [FIFO_DEPTH-1:0] inptr; +reg [FIFO_DEPTH-1:0] outptr; +reg [15:0] fifo_out; + +wire [FIFO_DEPTH:0] fifo_used = inptr >= outptr ? + inptr - outptr : + inptr - outptr + (2'd2**FIFO_DEPTH); + +assign hdd_cdda_req = fifo_used < ((2'd2**FIFO_DEPTH) - 16'd2352); + +always @(posedge clk_sys) begin + if (reset) + inptr <= 0; + else if (clk_en && hdd_cdda_wr) begin + fifo[inptr] <= {hdd_data_out[7:0], hdd_data_out[15:8]}; + inptr <= inptr + 1'd1; + end +end + +always @(posedge clk_sys) fifo_out <= fifo[outptr]; + +reg left = 0; +reg mute = 1; +reg fifo_active = 0; + +always @(posedge clk_sys) begin + if (reset) begin + outptr <= 0; + fifo_active <= 0; + mute <= 1; + left <= 0; + cdda_l <= 0; + cdda_r <= 0; + end else begin + if (cen_44100) begin + if (fifo_used >= 2352) + fifo_active <= 1; + if (outptr + 2'd2 == inptr) + fifo_active <= 0; + if (fifo_active) begin + outptr <= outptr + 1'd1; + left <= 1; + mute <= 0; + end else + mute <= 1; + end + if (left) begin + outptr <= outptr + 1'd1; + left <= 0; + end + + if (mute) begin + cdda_l <= 0; + cdda_r <= 0; + end else begin + if (left) + cdda_l <= fifo_out; + else + cdda_r <= fifo_out; + end + end +end + +endmodule diff --git a/common/mist/data_io.v b/common/mist/data_io.v index 7e14f129..6862f1f4 100644 --- a/common/mist/data_io.v +++ b/common/mist/data_io.v @@ -2,7 +2,7 @@ // data_io.v // // data_io for the MiST board -// http://code.google.com/p/mist-board/ +// https://github.com/mist-devel // // Copyright (c) 2014 Till Harbaum // @@ -30,6 +30,10 @@ module data_io input SPI_DI, inout SPI_DO, + input QCSn, + input QSCK, + input [3:0] QDAT, + input clkref_n, // assert ioctl_wr one cycle after clkref stobe (negative active) // ARM -> FPGA download @@ -43,52 +47,105 @@ module data_io output reg [7:0] ioctl_dout, input [7:0] ioctl_din, output reg [23:0] ioctl_fileext, // file extension - output reg [31:0] ioctl_filesize // file size + output reg [31:0] ioctl_filesize, // file size + + // IDE interface + input hdd_clk, + input hdd_cmd_req, + input hdd_cdda_req, + input hdd_dat_req, + output hdd_cdda_wr, + output hdd_status_wr, + output [2:0] hdd_addr = 0, + output hdd_wr, + + output [15:0] hdd_data_out, + input [15:0] hdd_data_in, + output hdd_data_rd, + output hdd_data_wr, + + // IDE config + output [1:0] hdd0_ena, + output [1:0] hdd1_ena ); parameter START_ADDR = 25'd0; parameter ROM_DIRECT_UPLOAD = 0; +parameter USE_QSPI = 0; +parameter ENABLE_IDE = 0; /////////////////////////////// DOWNLOADING /////////////////////////////// +reg [6:0] sbuf; reg [7:0] data_w; reg [7:0] data_w2 = 0; +reg [7:0] data_w3 = 0; reg [3:0] cnt; +reg [7:0] cmd; +reg [6:0] bytecnt; + reg rclk = 0; reg rclk2 = 0; +reg rclk3 = 0; reg addr_reset = 0; reg downloading_reg = 0; reg uploading_reg = 0; reg reg_do; -localparam DIO_FILE_TX = 8'h53; -localparam DIO_FILE_TX_DAT = 8'h54; -localparam DIO_FILE_INDEX = 8'h55; -localparam DIO_FILE_INFO = 8'h56; -localparam DIO_FILE_RX = 8'h57; -localparam DIO_FILE_RX_DAT = 8'h58; +localparam DIO_FILE_TX = 8'h53; +localparam DIO_FILE_TX_DAT = 8'h54; +localparam DIO_FILE_INDEX = 8'h55; +localparam DIO_FILE_INFO = 8'h56; +localparam DIO_FILE_RX = 8'h57; +localparam DIO_FILE_RX_DAT = 8'h58; + +localparam QSPI_READ = 8'h40; +localparam QSPI_WRITE = 8'h41; + +localparam CMD_IDE_REGS_RD = 8'h80; +localparam CMD_IDE_REGS_WR = 8'h90; +localparam CMD_IDE_DATA_WR = 8'hA0; +localparam CMD_IDE_DATA_RD = 8'hB0; +localparam CMD_IDE_CDDA_RD = 8'hC0; +localparam CMD_IDE_CDDA_WR = 8'hD0; +localparam CMD_IDE_STATUS_WR = 8'hF0; +localparam CMD_IDE_CFG_WR = 8'hFA; assign SPI_DO = reg_do; // data_io has its own SPI interface to the io controller +wire [7:0] cmdcode = { 4'h0, hdd_dat_req, hdd_cmd_req, 2'b00 }; + always@(negedge SPI_SCK or posedge SPI_SS2) begin : SPI_TRANSMITTER reg [7:0] dout_r; if(SPI_SS2) begin reg_do <= 1'bZ; end else begin - if (cnt == 15) dout_r <= ioctl_din; + if (cnt == 0) dout_r <= cmdcode; + if (cnt == 15) begin + case(cmd) + CMD_IDE_REGS_RD, + CMD_IDE_DATA_RD: + dout_r <= bytecnt[0] ? hdd_data_in[7:0] : hdd_data_in[15:8]; + + CMD_IDE_CDDA_RD: + dout_r <= {7'd0, hdd_cdda_req}; + + DIO_FILE_RX_DAT: + dout_r <= ioctl_din; + + default: + dout_r <= 0; + + endcase + end reg_do <= dout_r[~cnt[2:0]]; end end always@(posedge SPI_SCK, posedge SPI_SS2) begin : SPI_RECEIVER - reg [6:0] sbuf; - reg [24:0] addr; - reg [7:0] cmd; - reg [5:0] bytecnt; - if(SPI_SS2) begin bytecnt <= 0; cnt <= 0; @@ -105,6 +162,9 @@ always@(posedge SPI_SCK, posedge SPI_SS2) begin : SPI_RECEIVER if(cnt == 7) cmd <= {sbuf, SPI_DI}; if(cnt == 15) begin + if (~&bytecnt) bytecnt <= bytecnt + 1'd1; + else bytecnt[0] <= ~bytecnt[0]; + case (cmd) // prepare/end transmission DIO_FILE_TX: begin @@ -140,7 +200,6 @@ always@(posedge SPI_SCK, posedge SPI_SS2) begin : SPI_RECEIVER // receiving FAT directory entry (mist-firmware/fat.h - DIRENTRY) DIO_FILE_INFO: begin - bytecnt <= bytecnt + 1'd1; case (bytecnt) 8'h08: ioctl_fileext[23:16] <= {sbuf, SPI_DI}; 8'h09: ioctl_fileext[15: 8] <= {sbuf, SPI_DI}; @@ -157,7 +216,7 @@ always@(posedge SPI_SCK, posedge SPI_SS2) begin : SPI_RECEIVER end // direct SD Card->FPGA transfer -generate if (ROM_DIRECT_UPLOAD == 1) begin +generate if (ROM_DIRECT_UPLOAD || ENABLE_IDE) begin always@(posedge SPI_SCK, posedge SPI_SS4) begin : SPI_DIRECT_RECEIVER reg [6:0] sbuf2; @@ -192,19 +251,50 @@ end end endgenerate +// QSPI receiver +generate if (USE_QSPI) begin + +always@(negedge QSCK, posedge QCSn) begin : QSPI_RECEIVER + reg nibble_lo; + reg cmd_got; + reg cmd_write; + + if (QCSn) begin + cmd_got <= 0; + cmd_write <= 0; + nibble_lo <= 0; + end else begin + nibble_lo <= ~nibble_lo; + if (nibble_lo) begin + data_w3[3:0] <= QDAT; + if (!cmd_got) begin + cmd_got <= 1; + if ({data_w3[7:4], QDAT} == QSPI_WRITE) cmd_write <= 1; + end else begin + if (cmd_write) rclk3 <= ~rclk3; + end + end else + data_w3[7:4] <= QDAT; + end +end +end +endgenerate + always@(posedge clk_sys) begin : DATA_OUT // synchronisers reg rclkD, rclkD2; reg rclk2D, rclk2D2; + reg rclk3D, rclk3D2; reg addr_resetD, addr_resetD2; - reg wr_int, wr_int_direct, rd_int; + reg wr_int, wr_int_direct, wr_int_qspi, rd_int; reg [24:0] addr; reg [31:0] filepos; // bring flags from spi clock domain into core clock domain { rclkD, rclkD2 } <= { rclk, rclkD }; { rclk2D ,rclk2D2 } <= { rclk2, rclk2D }; + { rclk3D ,rclk3D2 } <= { rclk3, rclk3D }; { addr_resetD, addr_resetD2 } <= { addr_reset, addr_resetD }; ioctl_wr <= 0; @@ -224,8 +314,9 @@ always@(posedge clk_sys) begin : DATA_OUT rd_int <= 0; wr_int <= 0; wr_int_direct <= 0; - if (wr_int || wr_int_direct) begin - ioctl_dout <= wr_int ? data_w : data_w2; + wr_int_qspi <= 0; + if (wr_int || wr_int_direct || wr_int_qspi) begin + ioctl_dout <= wr_int ? data_w : wr_int_direct ? data_w2 : data_w3; ioctl_wr <= 1; addr <= addr + 1'd1; ioctl_addr <= addr; @@ -250,10 +341,203 @@ always@(posedge clk_sys) begin : DATA_OUT rd_int <= uploading_reg; end // direct transfer receiver - if (rclk2D ^ rclk2D2 && filepos != ioctl_filesize) begin + if (rclk2D ^ rclk2D2 && filepos != ioctl_filesize && downloading_reg) begin filepos <= filepos + 1'd1; wr_int_direct <= 1; end + // QSPI transfer receiver + if (rclk3D ^ rclk3D2) begin + wr_int_qspi <= downloading_reg; + end + end +// IDE handling +generate if (ENABLE_IDE) begin + +reg [1:0] int_hdd0_ena; +reg [1:0] int_hdd1_ena; +reg int_hdd_cdda_wr; +reg int_hdd_status_wr; +reg [2:0] int_hdd_addr = 0; +reg int_hdd_wr; +reg [15:0] int_hdd_data_out; +reg int_hdd_data_rd; +reg int_hdd_data_wr; + +assign hdd0_ena = int_hdd0_ena; +assign hdd1_ena = int_hdd1_ena; +assign hdd_cdda_wr = int_hdd_cdda_wr; +assign hdd_status_wr = int_hdd_status_wr; +assign hdd_addr = int_hdd_addr; +assign hdd_wr = int_hdd_wr; +assign hdd_data_out = int_hdd_data_out; +assign hdd_data_rd = int_hdd_data_rd; +assign hdd_data_wr = int_hdd_data_wr; + +reg rst0 = 1; +reg rst2 = 1; +reg rclk_ide_stat = 0; +reg rclk_ide_regs_rd = 0; +reg rclk_ide_regs_wr = 0; +reg rclk_ide_wr = 0; +reg rclk_ide_rd = 0; +reg rclk_cdda_wr = 0; +reg [7:0] data_ide; + +always@(posedge SPI_SCK, posedge SPI_SS2) begin : SPI_RECEIVER_IDE + if(SPI_SS2) begin + rst0 <= 1; + end else begin + rst0 <= 0; + + if(cnt == 15) begin + + case (cmd) + //IDE commands + CMD_IDE_CFG_WR: + if (bytecnt == 0) begin + int_hdd0_ena <= {sbuf[0], SPI_DI}; + int_hdd1_ena <= sbuf[2:1]; + end + + CMD_IDE_STATUS_WR: + if (bytecnt == 0) begin + data_ide <= {sbuf, SPI_DI}; + rclk_ide_stat <= ~rclk_ide_stat; + end + + CMD_IDE_REGS_WR: + if (bytecnt >= 8 && bytecnt <= 18 && !bytecnt[0]) begin + data_ide <= {sbuf, SPI_DI}; + rclk_ide_regs_wr <= ~rclk_ide_regs_wr; + end + + CMD_IDE_REGS_RD: + if (bytecnt > 5 && !bytecnt[0]) begin + rclk_ide_regs_rd <= ~rclk_ide_regs_rd; + end + + CMD_IDE_DATA_WR: + if (bytecnt > 4) begin + data_ide <= {sbuf, SPI_DI}; + rclk_ide_wr <= ~rclk_ide_wr; + end + + CMD_IDE_CDDA_WR: + if (bytecnt > 4) begin + data_ide <= {sbuf, SPI_DI}; + rclk_cdda_wr <= ~rclk_cdda_wr; + end + + CMD_IDE_DATA_RD: + if (bytecnt > 3) rclk_ide_rd <= ~rclk_ide_rd; + + endcase + end + end +end + +always@(posedge SPI_SCK, posedge SPI_SS4) begin : SPI_DIRECT_RECEIVER_IDE + if(SPI_SS4) begin + rst2 <= 1; + end else begin + rst2 <= 0; + end +end + +always@(posedge hdd_clk) begin : IDE_OUT + reg loword; + + // synchronisers + reg rclk2D, rclk2D2; + reg rclk_ide_statD, rclk_ide_statD2; + reg rclk_cdda_wrD, rclk_cdda_wrD2; + reg rclk_ide_wrD, rclk_ide_wrD2; + reg rclk_ide_rdD, rclk_ide_rdD2; + reg rclk_ide_regs_wrD, rclk_ide_regs_wrD2; + reg rclk_ide_regs_rdD, rclk_ide_regs_rdD2; + reg rst0D, rst0D2; + reg rst2D, rst2D2; + + // bring flags from spi clock domain into core clock domain + { rclk2D ,rclk2D2 } <= { rclk2, rclk2D }; + { rclk_ide_statD, rclk_ide_statD2 } <= { rclk_ide_stat, rclk_ide_statD }; + { rclk_ide_rdD, rclk_ide_rdD2 } <= { rclk_ide_rd, rclk_ide_rdD }; + { rclk_ide_wrD, rclk_ide_wrD2 } <= { rclk_ide_wr, rclk_ide_wrD }; + { rclk_cdda_wrD, rclk_cdda_wrD2 } <= { rclk_cdda_wr, rclk_cdda_wrD }; + { rclk_ide_regs_rdD, rclk_ide_regs_rdD2 } <= { rclk_ide_regs_rd, rclk_ide_regs_rdD }; + { rclk_ide_regs_wrD, rclk_ide_regs_wrD2 } <= { rclk_ide_regs_wr, rclk_ide_regs_wrD }; + { rst0D, rst0D2 } <= { rst0, rst0D }; + { rst2D, rst2D2 } <= { rst2, rst2D }; + + // IDE receiver + int_hdd_wr <= 0; + int_hdd_status_wr <= 0; + int_hdd_data_wr <= 0; + int_hdd_data_rd <= 0; + int_hdd_cdda_wr <= 0; + + if (rst0D2) int_hdd_addr <= 0; + if (rst0D2 && rst2D2) loword <= 0; + + if (rclk_ide_statD ^ rclk_ide_statD2) begin + int_hdd_status_wr <= 1; + int_hdd_data_out <= {8'h00, data_ide}; + end + if (rclk_ide_rdD ^ rclk_ide_rdD2) begin + loword <= ~loword; + if (loword) + int_hdd_data_rd <= 1; + end + if (rclk_ide_wrD ^ rclk_ide_wrD2) begin + loword <= ~loword; + if (!loword) + int_hdd_data_out[15:8] <= data_ide; + else begin + int_hdd_data_wr <= 1; + int_hdd_data_out[7:0] <= data_ide; + end + end + if (rclk_cdda_wrD ^ rclk_cdda_wrD2) begin + loword <= ~loword; + if (!loword) + int_hdd_data_out[15:8] <= data_ide; + else begin + int_hdd_cdda_wr <= 1; + int_hdd_data_out[7:0] <= data_ide; + end + end + if (rclk2D ^ rclk2D2 && !downloading_reg) begin + loword <= ~loword; + if (!loword) + int_hdd_data_out[15:8] <= data_w2; + else begin + int_hdd_data_wr <= 1; + int_hdd_data_out[7:0] <= data_w2; + end + end + if (rclk_ide_regs_wrD ^ rclk_ide_regs_wrD2) begin + int_hdd_wr <= 1; + int_hdd_data_out <= {8'h00, data_ide}; + int_hdd_addr <= int_hdd_addr + 1'd1; + end + if (rclk_ide_regs_rdD ^ rclk_ide_regs_rdD2) begin + int_hdd_addr <= int_hdd_addr + 1'd1; + end +end +end else begin + assign hdd0_ena = 0; + assign hdd1_ena = 0; + assign hdd_cdda_wr = 0; + assign hdd_status_wr = 0; + assign hdd_addr = 0; + assign hdd_wr = 0; + assign hdd_data_out = 0; + assign hdd_data_rd = 0; + assign hdd_data_wr = 0; +end + +endgenerate + endmodule diff --git a/common/mist/ide.v b/common/mist/ide.v new file mode 100644 index 00000000..393699c4 --- /dev/null +++ b/common/mist/ide.v @@ -0,0 +1,405 @@ +// Copyright 2008, 2009 by Jakub Bednarski +// +// Extracted from Minimig gayle.v +// +// Minimig is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// Minimig is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// +// +// -- JB -- +// +// 2008-10-06 - initial version +// 2008-10-08 - interrupt controller implemented, kickstart boots +// 2008-10-09 - working identify device command implemented (hdtoolbox detects our drive) +// - read command reads data from hardfile (fixed size and name, only one sector read size supported, workbench sees hardfile partition) +// 2008-10-10 - multiple sector transfer supported: works ok, sequential transfers with direct spi read and 28MHz CPU from 400 to 520 KB/s +// - arm firmare seekfile function very slow: seeking from start to 20MB takes 144 ms (some software improvements required) +// 2008-10-30 - write support added +// 2008-12-31 - added hdd enable +// 2009-05-24 - clean-up & renaming +// 2009-08-11 - hdd_ena enables Master & Slave drives +// 2009-11-18 - changed sector buffer size +// 2010-04-13 - changed sector buffer size +// 2010-08-10 - improved BSY signal handling +// 2022-08-18 - added packet command handling + +module ide +( + input clk, + input clk_en, + input reset, + input [2:0] address_in, + input sel_secondary, + input [15:0] data_in, + output [15:0] data_out, + output data_oe, + input rd, + input hwr, + input lwr, + input sel_ide, + output reg [1:0] intreq, + input [1:0] intreq_ack, // interrupt clear + output nrdy, // fifo is not ready for reading + input [1:0] hdd0_ena, // enables Master & Slave drives on primary channel + input [1:0] hdd1_ena, // enables Master & Slave drives on secondary channel + output fifo_rd, + output fifo_wr, + + // connection to the IO-Controller + output hdd_cmd_req, + output hdd_dat_req, + input [2:0] hdd_addr, + input [15:0] hdd_data_out, + output [15:0] hdd_data_in, + input hdd_wr, + input hdd_status_wr, + input hdd_data_wr, + input hdd_data_rd +); + +localparam VCC = 1'b1; +localparam GND = 1'b0; + +/* +0 Data +1 Error | Feature +2 SectorCount +3 SectorNumber +4 CylinderLow +5 CylinderHigh +6 Device/Head +7 Status | Command + +command class: +PI (PIO In) +PO (PIO Out) +ND (No Data) + +Status: +#6 - DRDY - Drive Ready +#7 - BSY - Busy +#3 - DRQ - Data Request +#0 - ERR - Error +INTRQ - Interrupt Request + +*/ + + +// address decoding signals +wire sel_tfr; // HDD task file registers select +wire sel_fifo; // HDD data port select (FIFO buffer) +wire sel_status /* synthesis keep */; // HDD status register select +wire sel_command /* synthesis keep */;// HDD command register select + +// internal registers +reg block_mark; // IDE multiple block start flag +reg busy; // busy status (command processing state) +reg pio_in; // pio in command type is being processed +reg pio_out; // pio out command type is being processed +reg error; // error status (command processing failed) + +reg [1:0] dev; // drive select (Primary/Secondary, Master/Slave) +wire bsy; // busy +wire drdy; // drive ready +wire drq; // data request +reg drq_d; // data request +wire err; // error +wire [7:0] status; // HDD status + +// FIFO control +wire fifo_reset; +wire [15:0] fifo_data_in; +wire [15:0] fifo_data_out; +wire fifo_full; +wire fifo_empty; +wire fifo_last_out; // last word of a sector is being read +wire fifo_last_in; // last word of a sector is being written + + +// HDD status register +assign status = {bsy,drdy,2'b01,drq,2'b00,err}; + +// packet states +reg [1:0] packet_state; +localparam PACKET_IDLE = 0; +localparam PACKET_WAITCMD = 1; +localparam PACKET_PROCESSCMD = 2; +wire packet_state_change; +reg [12:0] packet_count; +wire packet_in_last; +wire packet_in; +wire packet_out; + +`ifdef IDE_DEBUG +// cmd/status debug +reg [7:0] status_dbg /* synthesis noprune */; +reg [7:0] dbg_ide_cmd /* synthesis noprune */; +reg [2:0] dbg_addr /* synthesis noprune */; +reg dbg_wr /* synthesis noprune */; +reg[15:0] dbg_data_in /* synthesis noprune */; +reg[15:0] dbg_data_out /* synthesis noprune */; + +always @(posedge clk) begin + status_dbg <= status; + if (clk_en) begin + dbg_wr <= 0; + if (sel_command) // set when the CPU writes command register + dbg_ide_cmd <= data_in[15:8]; + if (sel_ide) begin + dbg_addr <= address_in; + dbg_wr <= hwr | lwr; + if (rd) dbg_data_out <= data_out; + if (hwr | lwr) dbg_data_in <= data_in; + end + end +end +`endif + +// HDD status register bits +assign bsy = busy & ~drq; +assign drdy = ~(bsy|drq); +assign err = error; + +// address decoding +assign sel_tfr = sel_ide; +assign sel_status = rd && sel_tfr && address_in==3'b111 ? VCC : GND; +assign sel_command = hwr && sel_tfr && address_in==3'b111 ? VCC : GND; +assign sel_fifo = sel_tfr && address_in==3'b000 ? VCC : GND; + +//===============================================================================================// + +// task file registers +reg [7:0] tfr [7:0]; +wire [2:0] tfr_sel; +wire [7:0] tfr_in; +wire [7:0] tfr_out; +wire tfr_we; + +reg [8:0] sector_count; // sector counter +wire sector_count_dec_in; // decrease sector counter (reads) +wire sector_count_dec_out; // decrease sector counter (writes) + +always @(posedge clk) + if (clk_en) begin + if (hwr && sel_tfr && address_in == 3'b010) begin // sector count register loaded by the host + sector_count <= {1'b0, data_in[15:8]}; + if (data_in[15:8] == 0) sector_count <= 9'd256; + end else if (sector_count_dec_in || sector_count_dec_out) + sector_count <= sector_count - 8'd1; + end + +reg rd_old; +reg wr_old; +reg sel_fifo_old; +always @(posedge clk) + if (clk_en) begin + rd_old <= rd; + wr_old <= hwr & lwr; + sel_fifo_old <= sel_fifo; + end + +assign sector_count_dec_in = pio_in & fifo_last_out & sel_fifo_old & ~rd & rd_old & packet_state == PACKET_IDLE; +assign sector_count_dec_out = pio_out & fifo_last_in & sel_fifo_old & ~hwr & ~lwr & wr_old & packet_state == PACKET_IDLE; + +// task file register control +assign tfr_we = packet_in_last ? 1'b1 : bsy ? hdd_wr : sel_tfr & hwr; +assign tfr_sel = packet_in_last ? 3'd2 : bsy ? hdd_addr : address_in; +assign tfr_in = packet_in_last ? 8'h03: bsy ? hdd_data_out[7:0] : data_in[15:8]; + +// input multiplexer for SPI host +assign hdd_data_in = tfr_sel==0 ? fifo_data_out : {7'h0, dev[1], tfr_out}; + +// task file registers +always @(posedge clk) + if (clk_en) begin + if (tfr_we) + tfr[tfr_sel] <= tfr_in; + end + +assign tfr_out = tfr[tfr_sel]; + +// master/slave drive select +always @(posedge clk) + if (clk_en) begin + if (reset) + dev <= 0; + else if (sel_tfr && address_in==6 && hwr) + dev <= {sel_secondary, data_in[12]}; + end + +assign packet_state_change = busy && hdd_status_wr && hdd_data_out[5]; + +// bytes count in a packet +always @(posedge clk) + if (clk_en) begin + if (reset) + packet_count <= 0; + else if (hdd_wr && hdd_addr == 4) + packet_count[6:0] <= hdd_data_out[7:1]; + else if (hdd_wr && hdd_addr == 5) + packet_count[12:7] <= hdd_data_out[5:0]; + else if (packet_state_change && packet_state == PACKET_IDLE) + packet_count <= 13'd6; // IDLE->WAITCMD transition, expect 6 words of packet command + end + +// status register (write only from SPI host) +// 7 - busy status (write zero to finish command processing: allow host access to task file registers) +// 6 +// 5 +// 4 - intreq (used for writes only) +// 3 - drq enable for pio in (PI) command type +// 2 - drq enable for pio out (PO) command type +// 1 +// 0 - error flag (remember about setting error task file register) + +// command busy status +always @(posedge clk) + if (clk_en) begin + if (reset) + busy <= GND; + else if (hdd_status_wr && hdd_data_out[7] || (sector_count_dec_in && sector_count == 9'h01)) // reset by SPI host (by clearing BSY status bit) + busy <= GND; + else if (sel_command) // set when the CPU writes command register + busy <= VCC; + end + +// IDE interrupt request register +always @(posedge clk) + if (clk_en) begin + drq_d <= drq; + + if (reset) begin + intreq[0] <= GND; + intreq[1] <= GND; + block_mark <= GND; + end else begin + if (busy && hdd_status_wr && hdd_data_out[3]) + block_mark <= VCC; // to handle IDENTIFY + + if (pio_in) begin // reads + if (hdd_status_wr && hdd_data_out[4]) + block_mark <= VCC; + if ((error | (!drq_d & drq)) & block_mark) begin + intreq[dev[1]] <= VCC; + block_mark <= GND; + end + if (packet_in_last) // read the last word from the packet command result + intreq[dev[1]] <= VCC; + end else if (pio_out) begin // writes + if (hdd_status_wr && hdd_data_out[4]) + intreq[dev[1]] <= VCC; + end else if (hdd_status_wr && hdd_data_out[7]) // other command types completed + intreq[dev[1]] <= VCC; + else if (packet_state_change && packet_state == PACKET_IDLE) // ready to accept command packet + intreq[dev[1]] <= VCC; + + if (intreq_ack[0]) intreq[0] <= GND; // cleared by the CPU + if (intreq_ack[1]) intreq[1] <= GND; // cleared by the CPU + + end + end + +// pio in command type +always @(posedge clk) + if (clk_en) begin + if (reset) + pio_in <= GND; + else if (drdy) // reset when processing of the current command ends + pio_in <= GND; + else if (busy && hdd_status_wr && hdd_data_out[3]) // set by SPI host + pio_in <= VCC; + end + +// pio out command type +always @(posedge clk) + if (clk_en) begin + if (reset) + pio_out <= GND; + else if (busy && hdd_status_wr && hdd_data_out[7]) // reset by SPI host when command processing completes + pio_out <= GND; + else if (busy && hdd_status_wr && hdd_data_out[3]) // pio_in set by SPI host (during PACKET processing) + pio_out <= GND; + else if (busy && hdd_status_wr && hdd_data_out[2]) // set by SPI host + pio_out <= VCC; + end + +// packet command state machine +always @(posedge clk) + if (clk_en) begin + if (reset) + packet_state <= PACKET_IDLE; + else if (drdy) // reset when processing of the current command ends + packet_state <= PACKET_IDLE; + else if (packet_state_change) // set by SPI host + packet_state <= packet_state == PACKET_IDLE ? PACKET_WAITCMD : + packet_state == PACKET_WAITCMD ? PACKET_PROCESSCMD : packet_state; + end + +assign drq = (fifo_full & pio_in) | (~fifo_full & pio_out & (sector_count != 0 || packet_out)); // HDD data request status bit + +// error status +always @(posedge clk) + if (clk_en) begin + if (reset) + error <= GND; + else if (sel_command) // reset by the CPU when command register is written + error <= GND; + else if (busy && hdd_status_wr && hdd_data_out[0]) // set by SPI host + error <= VCC; + end + +assign hdd_cmd_req = bsy; // bsy is set when command register is written, tells the SPI host about new command +assign hdd_dat_req = (fifo_full & pio_out); // the FIFO is full so SPI host may read it + +// FIFO in/out multiplexer +assign fifo_reset = reset | sel_command | packet_state_change | packet_in_last; +assign fifo_data_in = pio_in ? hdd_data_out : data_in; +assign fifo_rd = pio_out ? hdd_data_rd : sel_fifo & rd; +assign fifo_wr = pio_in ? hdd_data_wr : sel_fifo & hwr & lwr; + +assign packet_in = packet_state == PACKET_PROCESSCMD && pio_in; +assign packet_out = packet_state == PACKET_WAITCMD || (packet_state == PACKET_PROCESSCMD && pio_out); + +//sector data buffer (FIFO) +ide_fifo SECBUF1 +( + .clk(clk), + .clk_en(clk_en), + .reset(fifo_reset), + .data_in(fifo_data_in), + .data_out(fifo_data_out), + .rd(fifo_rd), + .wr(fifo_wr), + .packet_in(packet_in), + .packet_out(packet_out), + .packet_count(packet_count), + .packet_in_last(packet_in_last), + .full(fifo_full), + .empty(fifo_empty), + .last_out(fifo_last_out), + .last_in(fifo_last_in) +); + +// fifo is not ready for reading +assign nrdy = pio_in & sel_fifo & fifo_empty; + +assign data_oe = (!dev[1] && hdd0_ena[dev[0]]) || (dev[1] && hdd1_ena[dev[0]]); +//data_out multiplexer +assign data_out = sel_fifo && rd ? fifo_data_out : + sel_status ? data_oe ? {status,8'h00} : 16'h00_00 : + sel_tfr && rd ? {tfr_out,8'h00} : 16'h00_00; + +//===============================================================================================// + +endmodule diff --git a/common/mist/ide_fifo.v b/common/mist/ide_fifo.v new file mode 100644 index 00000000..24221efe --- /dev/null +++ b/common/mist/ide_fifo.v @@ -0,0 +1,86 @@ +module ide_fifo +( + input clk, // bus clock + input clk_en, + input reset, // reset + input [15:0] data_in, // data in + output reg [15:0] data_out, // data out + input rd, // read from fifo + input wr, // write to fifo + input packet_in, + input packet_out, + input [12:0] packet_count, + output packet_in_last, // last word is read in packet_in state + output full, // fifo is full + output empty, // fifo is empty + output last_out, // the last word of a sector is being read + output last_in // the last word of a sector is being written +); + +// local signals and registers +reg [15:0] mem [4095:0]; // 16 bit wide fifo memory +reg [12:0] inptr; // fifo input pointer +reg [12:0] outptr; // fifo output pointer +wire empty_rd; // fifo empty flag (set immediately after reading the last word) +reg empty_wr; // fifo empty flag (set one clock after writting the empty fifo) +reg rd_old; +reg wr_old; + +always @(posedge clk) + if (clk_en) begin + rd_old <= rd; + wr_old <= wr; + end + +// main fifo memory (implemented using synchronous block ram) +always @(posedge clk) + if (clk_en) begin + if (wr) + mem[inptr[11:0]] <= data_in; + end + +always @(posedge clk) + if (clk_en) + data_out <= mem[outptr[11:0]]; + +// fifo write pointer control +always @(posedge clk) + if (clk_en) begin + if (reset) + inptr <= 0; + else if (wr_old & ~wr) + inptr <= inptr + 1'd1; + end + +// fifo read pointer control +always @(posedge clk) + if (clk_en) begin + if (reset) + outptr <= 0; + else if (rd_old & ~rd) + outptr <= outptr + 1'd1; + end + +// the empty flag is set immediately after reading the last word from the fifo +assign empty_rd = inptr==outptr ? 1'b1 : 1'b0; + +// after writting empty fifo the empty flag is delayed by one clock to handle ram write delay +always @(posedge clk) + if (clk_en) + empty_wr <= empty_rd; + +assign empty = empty_rd | empty_wr; + +// at least 512 bytes are in FIFO +// this signal is activated when 512th byte is written to the empty fifo +// then it's deactivated when 512th byte is read from the fifo (hysteresis) +// special handlig of packet commands +assign full = (inptr[12:8] != outptr[12:8] && !packet_in && !packet_out) || + (packet_in && inptr == packet_count && inptr != outptr) || + (packet_out && inptr == packet_count /*&& inptr != outptr*/); +assign packet_in_last = packet_in && inptr == packet_count && inptr == outptr && inptr != 0; + +assign last_out = outptr[7:0] == 8'hFF ? 1'b1 : 1'b0; +assign last_in = inptr [7:0] == 8'hFF ? 1'b1 : 1'b0; + +endmodule diff --git a/common/mist/mist.qip b/common/mist/mist.qip index de360210..923b5289 100644 --- a/common/mist/mist.qip +++ b/common/mist/mist.qip @@ -7,4 +7,8 @@ set_global_assignment -name VERILOG_FILE [file join $::quartus(qip_path) osd.v] set_global_assignment -name VERILOG_FILE [file join $::quartus(qip_path) arcade_inputs.v] set_global_assignment -name VERILOG_FILE [file join $::quartus(qip_path) rgb2ypbpr.v] set_global_assignment -name SYSTEMVERILOG_FILE [file join $::quartus(qip_path) cofi.sv] +set_global_assignment -name VERILOG_FILE [file join $::quartus(qip_path) sd_card.v] +set_global_assignment -name VERILOG_FILE [file join $::quartus(qip_path) ide.v] +set_global_assignment -name VERILOG_FILE [file join $::quartus(qip_path) ide_fifo.v] +set_global_assignment -name VERILOG_FILE [file join $::quartus(qip_path) cdda_fifo.v] set_global_assignment -name VHDL_FILE [file join $::quartus(qip_path) dac.vhd] diff --git a/common/mist/mist.vhd b/common/mist/mist.vhd index d37c947c..2255c8b7 100644 --- a/common/mist/mist.vhd +++ b/common/mist/mist.vhd @@ -48,6 +48,7 @@ port ( sd_wr : in std_logic_vector(SD_IMAGES-1 downto 0) := (others => '0'); sd_ack : out std_logic; sd_ack_conf : out std_logic; + sd_ack_x : out std_logic_vector(SD_IMAGES-1 downto 0); sd_conf : in std_logic := '0'; sd_sdhc : in std_logic := '1'; img_size : out std_logic_vector(63 downto 0); @@ -99,7 +100,7 @@ port ( SPI_DI : in std_logic; scanlines : in std_logic_vector(1 downto 0); - ce_divider : in std_logic := '0'; + ce_divider : in std_logic_vector(2 downto 0) := "000"; scandoubler_disable : in std_logic; ypbpr : in std_logic; rotate : in std_logic_vector(1 downto 0); diff --git a/common/mist/mist_video.v b/common/mist/mist_video.v index 070ab50b..712b1dff 100644 --- a/common/mist/mist_video.v +++ b/common/mist/mist_video.v @@ -15,8 +15,9 @@ module mist_video // scanlines (00-none 01-25% 10-50% 11-75%) input [1:0] scanlines, - // non-scandoubled pixel clock divider 0 - clk_sys/4, 1 - clk_sys/2 - input ce_divider, + // non-scandoubled pixel clock divider: + // 0 - clk_sys/4, 1 - clk_sys/2, 2 - clk_sys/3, 3 - clk_sys/4, etc + input [2:0] ce_divider, // 0 = HVSync 31KHz, 1 = CSync 15KHz input scandoubler_disable, @@ -59,7 +60,7 @@ wire [5:0] SD_B_O; wire SD_HS_O; wire SD_VS_O; -wire pixel_ena; +wire pixel_ena; scandoubler #(SD_HCNT_WIDTH, COLOR_DEPTH) scandoubler ( diff --git a/common/mist/scandoubler.v b/common/mist/scandoubler.v index c9f0d684..8c54632b 100644 --- a/common/mist/scandoubler.v +++ b/common/mist/scandoubler.v @@ -18,12 +18,25 @@ // TODO: Delay vsync one line +// AMR - generates and output a pixel clock with a reliable phase relationship with +// with the scandoubled hsync pulse. Allows the incoming data to be sampled more +// sparsely, reducing block RAM usage. ce_x1/x2 are replaced with a ce_divider +// which is the largest value the counter will reach before resetting - so 3'111 to +// divide clk_sys by 8, 3'011 to divide by 4, 3'101 to divide by six. + +// Also now has a bypass mode, in which the incoming data will be scaled to the output +// width but otherwise unmodified. Simplifies the rest of the video chain. + + module scandoubler ( // system interface input clk_sys, + input bypass, - input ce_divider, + + // Pixelclock + input [2:0] ce_divider, // 0 - clk_sys/4, 1 - clk_sys/2, 2 - clk_sys/3, 3 - clk_sys/4, etc. output pixel_ena, // scanlines (00-none 01-25% 10-50% 11-75%) @@ -37,41 +50,16 @@ module scandoubler input [COLOR_DEPTH-1:0] b_in, // output interface - output reg hs_out, - output reg vs_out, - output reg [5:0] r_out, - output reg [5:0] g_out, - output reg [5:0] b_out + output hs_out, + output vs_out, + output [5:0] r_out, + output [5:0] g_out, + output [5:0] b_out ); -parameter HCNT_WIDTH = 9; -parameter COLOR_DEPTH = 6; - -// pixel clock divider -reg [1:0] i_div; -reg ce_x1, ce_x2; - -always @(posedge clk_sys) begin - reg last_hs_in; - last_hs_in <= hs_in; - if(last_hs_in & !hs_in) begin - i_div <= 2'b00; - end else begin - i_div <= i_div + 2'd1; - end -end - -always @(*) begin - if (!ce_divider) begin - ce_x1 = (i_div == 2'b01); - ce_x2 = i_div[0]; - end else begin - ce_x1 = i_div[0]; - ce_x2 = 1'b1; - end -end - -assign pixel_ena = bypass ? ce_x1 : ce_x2; +parameter HCNT_WIDTH = 9; // Resolution of scandoubler buffer +parameter COLOR_DEPTH = 6; // Bits per colour to be stored in the buffer +parameter HSCNT_WIDTH = 12; // Resolution of hsync counters // --------------------- create output signals ----------------- // latch everything once more to make it glitch free and apply scanline effect @@ -80,74 +68,85 @@ reg [5:0] r; reg [5:0] g; reg [5:0] b; +wire [5:0] r_o; +wire [5:0] g_o; +wire [5:0] b_o; +reg hs_o; +reg vs_o; + +wire [COLOR_DEPTH*3-1:0] sd_mux = bypass ? {r_in, g_in, b_in} : sd_out; + always @(*) begin if (COLOR_DEPTH == 6) begin - b = sd_out[5:0]; - g = sd_out[11:6]; - r = sd_out[17:12]; + b = sd_mux[5:0]; + g = sd_mux[11:6]; + r = sd_mux[17:12]; end else if (COLOR_DEPTH == 2) begin - b = {3{sd_out[1:0]}}; - g = {3{sd_out[3:2]}}; - r = {3{sd_out[5:4]}}; + b = {3{sd_mux[1:0]}}; + g = {3{sd_mux[3:2]}}; + r = {3{sd_mux[5:4]}}; end else if (COLOR_DEPTH == 1) begin - b = {6{sd_out[0]}}; - g = {6{sd_out[1]}}; - r = {6{sd_out[2]}}; + b = {6{sd_mux[0]}}; + g = {6{sd_mux[1]}}; + r = {6{sd_mux[2]}}; end else begin - b = { sd_out[COLOR_DEPTH-1:0], sd_out[COLOR_DEPTH-1 -:(6-COLOR_DEPTH)] }; - g = { sd_out[COLOR_DEPTH*2-1:COLOR_DEPTH], sd_out[COLOR_DEPTH*2-1 -:(6-COLOR_DEPTH)] }; - r = { sd_out[COLOR_DEPTH*3-1:COLOR_DEPTH*2], sd_out[COLOR_DEPTH*3-1 -:(6-COLOR_DEPTH)] }; + b = { sd_mux[COLOR_DEPTH-1:0], sd_mux[COLOR_DEPTH-1 -:(6-COLOR_DEPTH)] }; + g = { sd_mux[COLOR_DEPTH*2-1:COLOR_DEPTH], sd_mux[COLOR_DEPTH*2-1 -:(6-COLOR_DEPTH)] }; + r = { sd_mux[COLOR_DEPTH*3-1:COLOR_DEPTH*2], sd_mux[COLOR_DEPTH*3-1 -:(6-COLOR_DEPTH)] }; end end + +reg [12:0] r_mul; +reg [12:0] g_mul; +reg [12:0] b_mul; + +wire scanline_bypass = (!scanline) | (!(|scanlines)) | bypass; + +// More subtle variant of the scanlines effect. +// 0 00 -> 1000000 0x40 - bypass / inert mode +// 1 01 -> 0111010 0x3a - 25% +// 2 10 -> 0101110 0x2e - 50% +// 3 11 -> 0011010 0x1a - 75% + +wire [6:0] scanline_coeff = scanline_bypass ? + 7'b1000000 : {~(&scanlines),scanlines[0],1'b1,~scanlines[0],2'b10}; + always @(posedge clk_sys) begin - if(bypass) begin - r_out <= r; - g_out <= g; - b_out <= b; - hs_out <= hs_sd; - vs_out <= vs_sd; - end else if(ce_x2) begin - hs_out <= hs_sd; - vs_out <= vs_sd; + if(ce_x2) begin + hs_o <= hs_sd; + vs_o <= vs_in; // reset scanlines at every new screen - if(vs_out != vs_in) scanline <= 0; + if(vs_o != vs_in) scanline <= 0; // toggle scanlines at begin of every hsync - if(hs_out && !hs_sd) scanline <= !scanline; + if(hs_o && !hs_sd) scanline <= !scanline; - // if no scanlines or not a scanline - if(!scanline || !scanlines) begin - r_out <= r; - g_out <= g; - b_out <= b; - end else begin - case(scanlines) - 1: begin // reduce 25% = 1/2 + 1/4 - r_out <= {1'b0, r[5:1]} + {2'b00, r[5:2] }; - g_out <= {1'b0, g[5:1]} + {2'b00, g[5:2] }; - b_out <= {1'b0, b[5:1]} + {2'b00, b[5:2] }; - end - - 2: begin // reduce 50% = 1/2 - r_out <= {1'b0, r[5:1]}; - g_out <= {1'b0, g[5:1]}; - b_out <= {1'b0, b[5:1]}; - end - - 3: begin // reduce 75% = 1/4 - r_out <= {2'b00, r[5:2]}; - g_out <= {2'b00, g[5:2]}; - b_out <= {2'b00, b[5:2]}; - end - endcase - end + r_mul<=r*scanline_coeff; + g_mul<=g*scanline_coeff; + b_mul<=b*scanline_coeff; end end +assign r_o = r_mul[11:6]; +assign g_o = g_mul[11:6]; +assign b_o = b_mul[11:6]; + + +// Output multiplexing + +assign r_out = bypass ? r : r_o; +assign g_out = bypass ? g : g_o; +assign b_out = bypass ? b : b_o; +assign hs_out = bypass ? hs_in : hs_o; +assign vs_out = bypass ? vs_in : vs_o; + +assign pixel_ena = bypass ? ce_x1 : ce_x2; + + // scan doubler output register -wire [COLOR_DEPTH*3-1:0] sd_out = bypass ? sd_bypass_out : sd_buffer_out; +reg [COLOR_DEPTH*3-1:0] sd_out; // ================================================================== // ======================== the line buffers ======================== @@ -160,70 +159,104 @@ wire [COLOR_DEPTH*3-1:0] sd_out = bypass ? sd_bypass_out : sd_buffer_out; reg line_toggle; // total hsync time (in 16MHz cycles), hs_total reaches 1024 -reg [HCNT_WIDTH-1:0] hs_max; -reg [HCNT_WIDTH-1:0] hs_rise; reg [HCNT_WIDTH-1:0] hcnt; +reg [HSCNT_WIDTH:0] hs_max; +reg [HSCNT_WIDTH:0] hs_rise; +reg [HSCNT_WIDTH:0] synccnt; + +// Input pixel clock, aligned with input sync: +wire[2:0] ce_divider_adj = |ce_divider ? ce_divider : 3'd3; // 0 = clk/4 for compatiblity +reg [2:0] ce_divider_in; +reg [2:0] ce_divider_out; + +reg [2:0] i_div; +wire ce_x1 = (i_div == ce_divider_in); always @(posedge clk_sys) begin reg hsD, vsD; + // Pixel logic on x1 clkena if(ce_x1) begin - hsD <= hs_in; - - // falling edge of hsync indicates start of line - if(hsD && !hs_in) begin - hs_max <= hcnt; - hcnt <= 0; - end else begin - hcnt <= hcnt + 1'd1; - end - - // save position of rising edge - if(!hsD && hs_in) hs_rise <= hcnt; - - vsD <= vs_in; - if(vsD != vs_in) line_toggle <= 0; - - // begin of incoming hsync - if(hsD && !hs_in) line_toggle <= !line_toggle; - + hcnt <= hcnt + 1'd1; sd_buffer[{line_toggle, hcnt}] <= {r_in, g_in, b_in}; end + + // Generate pixel clock + i_div <= i_div + 1'd1; + + if (i_div==ce_divider_adj) i_div <= 3'b000; + + synccnt <= synccnt + 1'd1; + hsD <= hs_in; + if(hsD && !hs_in) begin + // At hsync latch the ce_divider counter limit for the input clock + // and pass the previous input clock limit to the output stage. + // This should give correct output if the pixel clock changes mid-screen. + ce_divider_out <= ce_divider_in; + ce_divider_in <= ce_divider_adj; + hs_max <= {1'b0,synccnt[HSCNT_WIDTH:1]}; + hcnt <= 0; + synccnt <= 0; + i_div <= 3'b000; + end + + // save position of rising edge + if(!hsD && hs_in) hs_rise <= {1'b0,synccnt[HSCNT_WIDTH:1]}; + + // begin of incoming hsync + if(hsD && !hs_in) line_toggle <= !line_toggle; + + vsD <= vs_in; + if(vsD != vs_in) line_toggle <= 0; + end // ================================================================== // ==================== output timing generation ==================== // ================================================================== -reg [COLOR_DEPTH*3-1:0] sd_buffer_out, sd_bypass_out; -reg [HCNT_WIDTH-1:0] sd_hcnt; -reg hs_sd, vs_sd; +reg [HSCNT_WIDTH:0] sd_synccnt; +reg [HCNT_WIDTH-1:0] sd_hcnt; +reg hs_sd; + +// Output pixel clock, aligned with output sync: +reg [2:0] sd_i_div; +wire ce_x2 = (sd_i_div == ce_divider_out) | (sd_i_div == {1'b0,ce_divider_out[2:1]}); // timing generation runs 32 MHz (twice the input signal analysis speed) always @(posedge clk_sys) begin reg hsD; + // Output logic on x2 clkena if(ce_x2) begin - hsD <= hs_in; - // output counter synchronous to input and at twice the rate sd_hcnt <= sd_hcnt + 1'd1; - if(hsD && !hs_in) sd_hcnt <= hs_max; - if(sd_hcnt == hs_max) sd_hcnt <= 0; - - // replicate horizontal sync at twice the speed - if(sd_hcnt == hs_max) hs_sd <= 0; - if(sd_hcnt == hs_rise) hs_sd <= 1; // read data from line sd_buffer - sd_buffer_out <= sd_buffer[{~line_toggle, sd_hcnt}]; - vs_sd <= vs_in; + sd_out <= sd_buffer[{~line_toggle, sd_hcnt}]; end - if(bypass) begin - sd_bypass_out <= {r_in, g_in, b_in}; - hs_sd <= hs_in; - vs_sd <= vs_in; + + // Framing logic on sysclk + sd_synccnt <= sd_synccnt + 1'd1; + hsD <= hs_in; + if(hsD && !hs_in) sd_synccnt <= hs_max; + + if(sd_synccnt == hs_max) begin + sd_synccnt <= 0; + sd_hcnt <= 0; end + + sd_i_div <= sd_i_div + 1'd1; + if (sd_i_div==ce_divider_adj) sd_i_div <= 3'b000; + + // replicate horizontal sync at twice the speed + if(sd_synccnt == 0) begin + hs_sd <= 0; + sd_i_div <= 3'b000; + end + + if(sd_synccnt == hs_rise) hs_sd <= 1; + end endmodule diff --git a/common/mist/sd_card.v b/common/mist/sd_card.v index 0d1ff58d..88fcbb30 100644 --- a/common/mist/sd_card.v +++ b/common/mist/sd_card.v @@ -503,6 +503,9 @@ always@(posedge clk_sys) begin reply_len <= 4'd4; end + // CMD59: CRC_ON_OFF + 8'h7b: reply <= 0; // ok + endcase end end diff --git a/common/mist/user_io.v b/common/mist/user_io.v index 3d283918..05e114dd 100644 --- a/common/mist/user_io.v +++ b/common/mist/user_io.v @@ -2,7 +2,7 @@ // user_io.v // // user_io for the MiST board -// http://code.google.com/p/mist-board/ +// https://github.com/mist-devel // // Copyright (c) 2014 Till Harbaum // @@ -57,14 +57,15 @@ module user_io ( input [31:0] sd_lba, input [SD_IMAGES-1:0] sd_rd, input [SD_IMAGES-1:0] sd_wr, - output reg sd_ack, - output reg sd_ack_conf, + output reg sd_ack = 0, // ack any transfer + output reg sd_ack_conf = 0, + output reg [SD_IMAGES-1:0] sd_ack_x = 0, // ack specific transfer input sd_conf, input sd_sdhc, output reg [7:0] sd_dout, // valid on rising edge of sd_dout_strobe - output reg sd_dout_strobe, + output reg sd_dout_strobe = 0, input [7:0] sd_din, - output reg sd_din_strobe, + output reg sd_din_strobe = 0, output reg [8:0] sd_buff_addr, output reg [SD_IMAGES-1:0] img_mounted, // rising edge if a new image is mounted @@ -72,11 +73,11 @@ module user_io ( // ps2 keyboard/mouse emulation output ps2_kbd_clk, - output reg ps2_kbd_data, + output ps2_kbd_data, input ps2_kbd_clk_i, input ps2_kbd_data_i, output ps2_mouse_clk, - output reg ps2_mouse_data, + output ps2_mouse_data, input ps2_mouse_clk_i, input ps2_mouse_data_i, @@ -86,6 +87,9 @@ module user_io ( output reg [7:0] key_code, // key scan code output reg key_strobe, // key data valid + input [7:0] kbd_out_data, // for Archie + input kbd_out_strobe, + // mouse data output reg [8:0] mouse_x, output reg [8:0] mouse_y, @@ -105,8 +109,10 @@ parameter ROM_DIRECT_UPLOAD=0; // direct upload used for file uploads from the A parameter SD_IMAGES=2; // number of block-access images (max. 4 supported in current firmware) parameter PS2BIDIR=0; // bi-directional PS2 interface parameter FEATURES=0; // requested features from the firmware +parameter ARCHIE=0; localparam W = $clog2(SD_IMAGES); +localparam PS2_FIFO_BITS = 4; reg [6:0] sbuf; reg [7:0] cmd; @@ -123,9 +129,8 @@ assign no_csync = but_sw[6]; assign conf_addr = byte_cnt; -// this variant of user_io is for 8 bit cores (type == a4) only // bit 4 indicates ROM direct upload capability -wire [7:0] core_type = ROM_DIRECT_UPLOAD ? 8'hb4 : 8'ha4; +wire [7:0] core_type = ARCHIE ? 8'ha6 : ROM_DIRECT_UPLOAD ? 8'hb4 : 8'ha4; reg [W:0] drive_sel; always begin @@ -140,9 +145,6 @@ wire [7:0] sd_cmd = { 4'h6, sd_conf, sd_sdhc, sd_wr[drive_sel], sd_rd[drive_sel] wire spi_sck = SPI_CLK; // ---------------- PS2 --------------------- -// 16 byte fifos to store ps2 bytes -localparam PS2_FIFO_BITS = 4; - reg ps2_clk; always @(posedge clk_sys) begin integer cnt; @@ -154,237 +156,44 @@ always @(posedge clk_sys) begin end // keyboard -reg [7:0] ps2_kbd_fifo [(2**PS2_FIFO_BITS)-1:0]; -reg [PS2_FIFO_BITS-1:0] ps2_kbd_wptr; -reg [PS2_FIFO_BITS-1:0] ps2_kbd_rptr; +reg ps2_kbd_tx_strobe; +wire [7:0] ps2_kbd_rx_byte ; +wire ps2_kbd_rx_strobe; +wire ps2_kbd_fifo_ok; -// ps2 transmitter state machine -reg [3:0] ps2_kbd_tx_state; -reg [7:0] ps2_kbd_tx_byte; -reg ps2_kbd_parity; - -// ps2 receiver state machine -reg [3:0] ps2_kbd_rx_state = 0; -reg [1:0] ps2_kbd_rx_start = 0; -reg [7:0] ps2_kbd_rx_byte = 0; -reg ps2_kbd_rx_strobe = 0; - -assign ps2_kbd_clk = ps2_clk || (ps2_kbd_tx_state == 0 && ps2_kbd_rx_state == 0); - -// ps2 transmitter/receiver -// Takes a byte from the FIFO and sends it in a ps2 compliant serial format. -// Sends a command to the IO controller if bidirectional mode is enabled. -always@(posedge clk_sys) begin : ps2_kbd - - reg ps2_clkD; - reg ps2_clk_iD, ps2_dat_iD; - reg ps2_kbd_r_inc; - - // send data - ps2_clkD <= ps2_clk; - if (~ps2_clkD & ps2_clk) begin - ps2_kbd_r_inc <= 1'b0; - - if(ps2_kbd_r_inc) - ps2_kbd_rptr <= ps2_kbd_rptr + 1'd1; - - // transmitter is idle? - if(ps2_kbd_tx_state == 0) begin - ps2_kbd_data <= 1; - // data in fifo present? - if(ps2_kbd_wptr != ps2_kbd_rptr && (ps2_kbd_clk_i | !PS2BIDIR)) begin - // load tx register from fifo - ps2_kbd_tx_byte <= ps2_kbd_fifo[ps2_kbd_rptr]; - ps2_kbd_r_inc <= 1'b1; - - // reset parity - ps2_kbd_parity <= 1'b1; - - // start transmitter - ps2_kbd_tx_state <= 4'd1; - - // put start bit on data line - ps2_kbd_data <= 1'b0; // start bit is 0 - end - end else begin - - // transmission of 8 data bits - if((ps2_kbd_tx_state >= 1)&&(ps2_kbd_tx_state < 9)) begin - ps2_kbd_data <= ps2_kbd_tx_byte[0]; // data bits - ps2_kbd_tx_byte[6:0] <= ps2_kbd_tx_byte[7:1]; // shift down - if(ps2_kbd_tx_byte[0]) - ps2_kbd_parity <= !ps2_kbd_parity; - end - - // transmission of parity - if(ps2_kbd_tx_state == 9) - ps2_kbd_data <= ps2_kbd_parity; - - // transmission of stop bit - if(ps2_kbd_tx_state == 10) - ps2_kbd_data <= 1'b1; // stop bit is 1 - - // advance state machine - if(ps2_kbd_tx_state < 11) - ps2_kbd_tx_state <= ps2_kbd_tx_state + 4'd1; - else - ps2_kbd_tx_state <= 4'd0; - end - end - - if (PS2BIDIR) begin - ps2_clk_iD <= ps2_kbd_clk_i; - ps2_dat_iD <= ps2_kbd_data_i; - - // receive command - case (ps2_kbd_rx_start) - 2'd0: - // first: host pulls down the clock line - if (ps2_clk_iD & ~ps2_kbd_clk_i) ps2_kbd_rx_start <= 1; - 2'd1: - // second: host pulls down the data line, while releasing the clock - if (ps2_dat_iD && !ps2_kbd_data_i) ps2_kbd_rx_start <= 2'd2; - // if it releases the clock without pulling down the data line: goto 0 - else if (ps2_kbd_clk_i) ps2_kbd_rx_start <= 0; - 2'd2: - if (ps2_clkD && ~ps2_clk) begin - ps2_kbd_rx_state <= 4'd1; - ps2_kbd_rx_start <= 0; - end - default: ; - endcase - - // host data is valid after the rising edge of the clock - if(ps2_kbd_rx_state != 0 && ~ps2_clkD && ps2_clk) begin - ps2_kbd_rx_state <= ps2_kbd_rx_state + 1'd1; - if (ps2_kbd_rx_state == 9) ;// parity - else if (ps2_kbd_rx_state == 10) begin - ps2_kbd_data <= 0; // ack the received byte - end else if (ps2_kbd_rx_state == 11) begin - ps2_kbd_rx_state <= 0; - ps2_kbd_rx_strobe <= ~ps2_kbd_rx_strobe; - end else begin - ps2_kbd_rx_byte <= {ps2_kbd_data_i, ps2_kbd_rx_byte[7:1]}; - end - end - end -end +user_io_ps2 #(.PS2_BIDIR(PS2BIDIR), .PS2_FIFO_BITS(4)) ps2_kbd ( + .clk_sys ( clk_sys ), + .ps2_clk ( ps2_clk ), + .ps2_clk_i ( ps2_kbd_clk_i ), + .ps2_clk_o ( ps2_kbd_clk ), + .ps2_data_i ( ps2_kbd_data_i ), + .ps2_data_o ( ps2_kbd_data ), + .ps2_tx_strobe ( ps2_kbd_tx_strobe ), // from IO controller + .ps2_tx_byte ( spi_byte_in ), + .ps2_rx_strobe ( ps2_kbd_rx_strobe ), // to IO controller + .ps2_rx_byte ( ps2_kbd_rx_byte ), + .ps2_fifo_ready( ps2_kbd_fifo_ok ) +); // mouse -reg [7:0] ps2_mouse_fifo [(2**PS2_FIFO_BITS)-1:0]; -reg [PS2_FIFO_BITS-1:0] ps2_mouse_wptr; -reg [PS2_FIFO_BITS-1:0] ps2_mouse_rptr; +reg ps2_mouse_tx_strobe; +wire [7:0] ps2_mouse_rx_byte ; +wire ps2_mouse_rx_strobe; +wire ps2_mouse_fifo_ok; -// ps2 transmitter state machine -reg [3:0] ps2_mouse_tx_state; -reg [7:0] ps2_mouse_tx_byte; -reg ps2_mouse_parity; - -// ps2 receiver state machine -reg [3:0] ps2_mouse_rx_state = 0; -reg [1:0] ps2_mouse_rx_start = 0; -reg [7:0] ps2_mouse_rx_byte = 0; -reg ps2_mouse_rx_strobe = 0; - -assign ps2_mouse_clk = ps2_clk || (ps2_mouse_tx_state == 0 && ps2_mouse_rx_state == 0); - -// ps2 transmitter/receiver -// Takes a byte from the FIFO and sends it in a ps2 compliant serial format. -// Sends a command to the IO controller if bidirectional mode is enabled. -always@(posedge clk_sys) begin : ps2_mouse - reg ps2_clkD; - reg ps2_clk_iD, ps2_dat_iD; - reg ps2_mouse_r_inc; - - ps2_clkD <= ps2_clk; - if (~ps2_clkD & ps2_clk) begin - ps2_mouse_r_inc <= 1'b0; - - if(ps2_mouse_r_inc) - ps2_mouse_rptr <= ps2_mouse_rptr + 1'd1; - - // transmitter is idle? - if(ps2_mouse_tx_state == 0) begin - ps2_mouse_data <= 1; - // data in fifo present? - if(ps2_mouse_wptr != ps2_mouse_rptr && (ps2_mouse_clk_i | !PS2BIDIR)) begin - // load tx register from fifo - ps2_mouse_tx_byte <= ps2_mouse_fifo[ps2_mouse_rptr]; - ps2_mouse_r_inc <= 1'b1; - - // reset parity - ps2_mouse_parity <= 1'b1; - - // start transmitter - ps2_mouse_tx_state <= 4'd1; - - // put start bit on data line - ps2_mouse_data <= 1'b0; // start bit is 0 - end - end else begin - - // transmission of 8 data bits - if((ps2_mouse_tx_state >= 1)&&(ps2_mouse_tx_state < 9)) begin - ps2_mouse_data <= ps2_mouse_tx_byte[0]; // data bits - ps2_mouse_tx_byte[6:0] <= ps2_mouse_tx_byte[7:1]; // shift down - if(ps2_mouse_tx_byte[0]) - ps2_mouse_parity <= !ps2_mouse_parity; - end - - // transmission of parity - if(ps2_mouse_tx_state == 9) - ps2_mouse_data <= ps2_mouse_parity; - - // transmission of stop bit - if(ps2_mouse_tx_state == 10) - ps2_mouse_data <= 1'b1; // stop bit is 1 - - // advance state machine - if(ps2_mouse_tx_state < 11) - ps2_mouse_tx_state <= ps2_mouse_tx_state + 4'd1; - else - ps2_mouse_tx_state <= 4'd0; - end - end - - if (PS2BIDIR) begin - - ps2_clk_iD <= ps2_mouse_clk_i; - ps2_dat_iD <= ps2_mouse_data_i; - - // receive command - case (ps2_mouse_rx_start) - 2'd0: - // first: host pulls down the clock line - if (ps2_clk_iD & ~ps2_mouse_clk_i) ps2_mouse_rx_start <= 1; - 2'd1: - // second: host pulls down the data line, while releasing the clock - if (ps2_dat_iD && !ps2_mouse_data_i) ps2_mouse_rx_start <= 2'd2; - // if it releases the clock without pulling down the data line: goto 0 - else if (ps2_mouse_clk_i) ps2_mouse_rx_start <= 0; - 2'd2: - if (ps2_clkD && ~ps2_clk) begin - ps2_mouse_rx_state <= 4'd1; - ps2_mouse_rx_start <= 0; - end - default: ; - endcase - - // host data is valid after the rising edge of the clock - if(ps2_mouse_rx_state != 0 && ~ps2_clkD && ps2_clk) begin - ps2_mouse_rx_state <= ps2_mouse_rx_state + 1'd1; - if (ps2_mouse_rx_state == 9) ;// parity - else if (ps2_mouse_rx_state == 10) begin - ps2_mouse_data <= 0; // ack the received byte - end else if (ps2_mouse_rx_state == 11) begin - ps2_mouse_rx_state <= 0; - ps2_mouse_rx_strobe <= ~ps2_mouse_rx_strobe; - end else begin - ps2_mouse_rx_byte <= {ps2_mouse_data_i, ps2_mouse_rx_byte[7:1]}; - end - end - end -end +user_io_ps2 #(.PS2_BIDIR(PS2BIDIR), .PS2_FIFO_BITS(3)) ps2_mouse ( + .clk_sys ( clk_sys ), + .ps2_clk ( ps2_clk ), + .ps2_clk_i ( ps2_mouse_clk_i ), + .ps2_clk_o ( ps2_mouse_clk ), + .ps2_data_i ( ps2_mouse_data_i ), + .ps2_data_o ( ps2_mouse_data ), + .ps2_tx_strobe ( ps2_mouse_tx_strobe ), // from IO controller + .ps2_tx_byte ( spi_byte_in ), + .ps2_rx_strobe ( ps2_mouse_rx_strobe ), // to IO controller + .ps2_rx_byte ( ps2_mouse_rx_byte ), + .ps2_fifo_ready( ps2_mouse_fifo_ok ) +); // fifo to receive serial data from core to be forwarded to io controller @@ -446,6 +255,23 @@ always@(negedge spi_sck or posedge SPI_SS_IO) begin : spi_byteout end end +generate if (ARCHIE) begin +reg [7:0] kbd_out_status; +reg [7:0] kbd_out_data_r; +reg kbd_out_data_available = 0; + +always@(negedge spi_sck or posedge SPI_SS_IO) begin : archie_kbd_out + if(SPI_SS_IO == 1) begin + kbd_out_data_r <= 0; + kbd_out_status <= 0; + end else begin + kbd_out_status <= { 4'ha, 3'b000, kbd_out_data_available }; + kbd_out_data_r <= kbd_out_data; + end +end +end +endgenerate + always@(posedge spi_sck or posedge SPI_SS_IO) begin : spi_transmitter reg [31:0] sd_lba_r; reg [W:0] drive_sel_r; @@ -461,6 +287,11 @@ always@(posedge spi_sck or posedge SPI_SS_IO) begin : spi_transmitter spi_byte_out <= 0; case({(!byte_cnt) ? {sbuf, SPI_MOSI} : cmd}) + 8'h04: if (ARCHIE) begin + if(byte_cnt == 0) spi_byte_out <= kbd_out_status; + else spi_byte_out <= kbd_out_data_r; + end + // PS2 keyboard command 8'h0e: if (byte_cnt == 0) begin ps2_kbd_rx_strobeD <= ps2_kbd_rx_strobe; @@ -539,12 +370,14 @@ always @(posedge clk_sys) begin : cmd_block reg spi_receiver_strobeD; reg spi_transfer_endD; reg [7:0] acmd; - reg [7:0] abyte_cnt; // counts bytes + reg [3:0] abyte_cnt; // counts bytes reg [7:0] mouse_flags_r; reg [7:0] mouse_x_r; reg [7:0] mouse_y_r; + reg mouse_fifo_ok; + reg kbd_fifo_ok; reg key_pressed_r; reg key_extended_r; @@ -556,17 +389,46 @@ always @(posedge clk_sys) begin : cmd_block key_strobe <= 0; mouse_strobe <= 0; + ps2_kbd_tx_strobe <= 0; + ps2_mouse_tx_strobe <= 0; + + if(ARCHIE) begin + if (kbd_out_strobe) kbd_out_data_available <= 1; + key_pressed <= 0; + key_extended <= 0; + mouse_x <= 0; + mouse_y <= 0; + mouse_z <= 0; + mouse_flags <= 0; + mouse_idx <= 0; + end if (spi_transfer_end) begin - abyte_cnt <= 8'd0; + abyte_cnt <= 0; + mouse_fifo_ok <= 0; + kbd_fifo_ok <= 0; end else if (spi_receiver_strobeD ^ spi_receiver_strobe) begin if(~&abyte_cnt) - abyte_cnt <= abyte_cnt + 8'd1; + abyte_cnt <= abyte_cnt + 1'd1; if(abyte_cnt == 0) begin acmd <= spi_byte_in; + if (spi_byte_in == 8'h70 || spi_byte_in == 8'h71) + // accept the incoming mouse data only if there's place for the full packet + mouse_fifo_ok <= ps2_mouse_fifo_ok; + if (spi_byte_in == 8'h05) + // accept the incoming keyboard data only if there's place for the full packet + kbd_fifo_ok <= ps2_kbd_fifo_ok; end else begin + if (ARCHIE) begin + if(acmd == 8'h04) kbd_out_data_available <= 0; + if(acmd == 8'h05) begin + key_strobe <= 1; + key_code <= spi_byte_in; + end + end + case(acmd) // buttons and switches 8'h01: but_sw <= spi_byte_in; @@ -575,11 +437,10 @@ always @(posedge clk_sys) begin : cmd_block 8'h62: if (abyte_cnt < 5) joystick_2[(abyte_cnt-1)<<3 +:8] <= spi_byte_in; 8'h63: if (abyte_cnt < 5) joystick_3[(abyte_cnt-1)<<3 +:8] <= spi_byte_in; 8'h64: if (abyte_cnt < 5) joystick_4[(abyte_cnt-1)<<3 +:8] <= spi_byte_in; - 8'h70,8'h71: begin + 8'h70,8'h71: if (!ARCHIE) begin // store incoming ps2 mouse bytes - if (abyte_cnt < 4) begin - ps2_mouse_fifo[ps2_mouse_wptr] <= spi_byte_in; - ps2_mouse_wptr <= ps2_mouse_wptr + 1'd1; + if (abyte_cnt < 4 && mouse_fifo_ok) begin + ps2_mouse_tx_strobe <= 1; end if (abyte_cnt == 1) mouse_flags_r <= spi_byte_in; @@ -595,10 +456,9 @@ always @(posedge clk_sys) begin : cmd_block mouse_strobe <= 1; end end - 8'h05: begin - // store incoming ps2 keyboard bytes - ps2_kbd_fifo[ps2_kbd_wptr] <= spi_byte_in; - ps2_kbd_wptr <= ps2_kbd_wptr + 1'd1; + 8'h05: if (!ARCHIE) begin + // store incoming ps2 keyboard bytes + if (kbd_fifo_ok) ps2_kbd_tx_strobe <= 1; if (abyte_cnt == 1) begin key_extended_r <= 0; key_pressed_r <= 1; @@ -698,6 +558,7 @@ always @(posedge clk_sd) begin : sd_block sd_ack <= 1'b0; sd_ack_conf <= 1'b0; sd_buff_addr <= 0; + if (acmd == 8'h17 || acmd == 8'h18) sd_ack_x <= 0; end else if (spi_receiver_strobeD ^ spi_receiver_strobe) begin if(~&abyte_cnt) @@ -740,9 +601,160 @@ always @(posedge clk_sd) begin : sd_block // send image info 8'h1d: if(abyte_cnt<9) img_size[(abyte_cnt-1)<<3 +:8] <= spi_byte_in; + // data transfer ack + 8'h23: sd_ack_x <= 1'b1 << spi_byte_in; + endcase end end end endmodule + +module user_io_ps2 ( + input clk_sys, + input ps2_clk, + input ps2_clk_i, + output ps2_clk_o, + input ps2_data_i, + output reg ps2_data_o = 1, + input ps2_tx_strobe, // from IO controller + input [7:0] ps2_tx_byte, + output reg ps2_rx_strobe = 0, // to IO controller + output reg [7:0] ps2_rx_byte = 0, + output ps2_fifo_ready +); + +parameter PS2_FIFO_BITS = 4; +parameter PS2_BIDIR = 0; + +reg [7:0] ps2_fifo [(2**PS2_FIFO_BITS)-1:0]; +reg [PS2_FIFO_BITS-1:0] ps2_wptr; +reg [PS2_FIFO_BITS-1:0] ps2_rptr; +wire [PS2_FIFO_BITS:0] ps2_used = ps2_wptr >= ps2_rptr ? + ps2_wptr - ps2_rptr : + ps2_wptr - ps2_rptr + (2'd2**PS2_FIFO_BITS); +wire [PS2_FIFO_BITS:0] ps2_free = (2'd2**PS2_FIFO_BITS) - ps2_used; + +assign ps2_fifo_ready = ps2_free[PS2_FIFO_BITS:2] != 0; // ps2_free > 3 + +// ps2 transmitter state machine +reg [3:0] ps2_tx_state; +reg [7:0] ps2_tx_shift_reg; +reg ps2_parity; + +// ps2 receiver state machine +reg [3:0] ps2_rx_state = 0; +reg [1:0] ps2_rx_start = 0; + +assign ps2_clk_o = ps2_clk || (ps2_tx_state == 0 && ps2_rx_state == 0); + +always@(posedge clk_sys) begin : ps2_fifo_wr + if (ps2_tx_strobe) begin + ps2_fifo[ps2_wptr] <= ps2_tx_byte; + ps2_wptr <= ps2_wptr + 1'd1; + end +end + +// ps2 transmitter/receiver +// Takes a byte from the FIFO and sends it in a ps2 compliant serial format. +// Sends a command to the IO controller if bidirectional mode is enabled. +always@(posedge clk_sys) begin : ps2_txrx + reg ps2_clkD; + reg ps2_clk_iD, ps2_dat_iD; + reg ps2_r_inc; + + ps2_clkD <= ps2_clk; + if (~ps2_clkD & ps2_clk) begin + ps2_r_inc <= 1'b0; + + if(ps2_r_inc) + ps2_rptr <= ps2_rptr + 1'd1; + + // transmitter is idle? + if(ps2_tx_state == 0) begin + ps2_data_o <= 1; + // data in fifo present? + if(ps2_wptr != ps2_rptr && (ps2_clk_i | !PS2_BIDIR)) begin + // load tx register from fifo + ps2_tx_shift_reg <= ps2_fifo[ps2_rptr]; + ps2_r_inc <= 1'b1; + + // reset parity + ps2_parity <= 1'b1; + + // start transmitter + ps2_tx_state <= 4'd1; + + // put start bit on data line + ps2_data_o <= 1'b0; // start bit is 0 + end + end else begin + + // transmission of 8 data bits + if((ps2_tx_state >= 1)&&(ps2_tx_state < 9)) begin + ps2_data_o <= ps2_tx_shift_reg[0]; // data bits + ps2_tx_shift_reg[6:0] <= ps2_tx_shift_reg[7:1]; // shift down + if(ps2_tx_shift_reg[0]) + ps2_parity <= !ps2_parity; + end + + // transmission of parity + if(ps2_tx_state == 9) + ps2_data_o <= ps2_parity; + + // transmission of stop bit + if(ps2_tx_state == 10) + ps2_data_o <= 1'b1; // stop bit is 1 + + // advance state machine + if(ps2_tx_state == 11) + ps2_tx_state <= 4'd0; + else + ps2_tx_state <= ps2_tx_state + 4'd1; + end + end + + if (PS2_BIDIR) begin + + ps2_clk_iD <= ps2_clk_i; + ps2_dat_iD <= ps2_data_i; + + // receive command + case (ps2_rx_start) + 2'd0: + // first: host pulls down the clock line + if (ps2_clk_iD & ~ps2_clk_i) ps2_rx_start <= 1; + 2'd1: + // second: host pulls down the data line, while releasing the clock + if (ps2_dat_iD && !ps2_data_i) ps2_rx_start <= 2'd2; + // if it releases the clock without pulling down the data line: goto 0 + else if (ps2_clk_i) ps2_rx_start <= 0; + 2'd2: + if (ps2_clkD && ~ps2_clk) begin + ps2_rx_state <= 4'd1; + ps2_rx_start <= 0; + end + default: ; + endcase + + // host data is valid after the rising edge of the clock + if(ps2_rx_state != 0 && ~ps2_clkD && ps2_clk) begin + ps2_rx_state <= ps2_rx_state + 1'd1; + if (ps2_rx_state == 9) ;// parity + else if (ps2_rx_state == 10) begin + ps2_data_o <= 0; // ack the received byte + end else if (ps2_rx_state == 11) begin + ps2_rx_state <= 0; + ps2_rx_strobe <= ~ps2_rx_strobe; + end else begin + ps2_rx_byte <= {ps2_data_i, ps2_rx_byte[7:1]}; + end + end + end else begin + ps2_rx_byte <= 0; + ps2_rx_strobe <= 0; + end +end + +endmodule