1
0
mirror of https://github.com/mist-devel/mist-board.git synced 2026-02-06 08:04:41 +00:00

[Gameboy] Improve timings

- Move to 64 MHz SDRAM clock (for later GBC support)
- Update user_io and data_io
This commit is contained in:
Gyorgy Szombathelyi
2019-02-10 17:31:45 +01:00
parent 238678366b
commit 9f55c98ce3
10 changed files with 978 additions and 625 deletions

View File

@@ -1,10 +1,12 @@
//
// data_io.v
//
// io controller writable ram for the MiST board
// https://github.com/mist-devel
// data_io for the MiST board
// http://code.google.com/p/mist-board/
//
// Copyright (c) 2015 Till Harbaum <till@harbaum.org>
// Copyright (c) 2014 Till Harbaum <till@harbaum.org>
// Copyright (c) 2015-2017 Sorgelig
// Copyright (c) 2019 György 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
@@ -19,99 +21,124 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
///////////////////////////////////////////////////////////////////////
module data_io (
// io controller spi interface
input sck,
input ss,
input sdi,
module data_io
(
// Global clock. It should be around 100MHz (higher is better).
input clk_sys,
output downloading, // signal indicating an active download
output reg [4:0] index, // menu index used to upload the file
// external ram interface
input clk,
output reg wr,
output reg [23:0] addr,
output reg [15:0] data
// Global SPI clock from ARM. 24MHz
input SPI_SCK,
input SPI_SS2,
input SPI_DI,
// ARM -> FPGA download
input ioctl_clkref,
output reg ioctl_download = 0, // signal indicating an active download
output reg [7:0] ioctl_index, // menu index used to upload the file
output reg ioctl_wr = 0,
output reg [24:0] ioctl_addr,
output reg [15:0] ioctl_dout
);
// *********************************************************************************
// spi client
// *********************************************************************************
/////////////////////////////// DOWNLOADING ///////////////////////////////
// this core supports only the display related OSD commands
// of the minimig
reg [14:0] sbuf;
reg [7:0] cmd;
reg [4:0] cnt;
reg rclk;
reg [23:0] laddr;
reg [15:0] ldata;
localparam UIO_FILE_TX = 8'h53;
localparam UIO_FILE_TX_DAT = 8'h54;
localparam UIO_FILE_INDEX = 8'h55;
// SPI receiver IO -> FPGA
assign downloading = downloading_reg;
reg downloading_reg = 1'b0;
reg spi_receiver_strobe_r = 0;
reg spi_transfer_end_r = 1;
reg [7:0] spi_byte_in;
// data_io has its own SPI interface to the io controller
always@(posedge sck, posedge ss) begin
if(ss == 1'b1)
cnt <= 5'd0;
else begin
rclk <= 1'b0;
// Read at spi_sck clock domain, assemble bytes for transferring to clk_sys
always@(posedge SPI_SCK or posedge SPI_SS2) begin
// don't shift in last bit. It is evaluated directly
// when writing to ram
if(cnt != 23)
sbuf <= { sbuf[13:0], sdi};
// count 0-7 8-15 16-23 8-15 16-23 ...
if(cnt < 23) cnt <= cnt + 4'd1;
else cnt <= 4'd8;
reg [6:0] sbuf;
reg [2:0] bit_cnt;
// finished command byte
if(cnt == 7)
cmd <= {sbuf[6:0], sdi};
// prepare/end transmission
if((cmd == UIO_FILE_TX) && (cnt == 15)) begin
// prepare
if(sdi) begin
// download rom to address 0
laddr <= 24'h0 - 24'd1;
downloading_reg <= 1'b1;
end else
downloading_reg <= 1'b0;
end
if(SPI_SS2) begin
spi_transfer_end_r <= 1;
bit_cnt <= 0;
end else begin
spi_transfer_end_r <= 0;
// command 0x54: UIO_FILE_TX
if((cmd == UIO_FILE_TX_DAT) && (cnt == 23)) begin
ldata <= {sbuf, sdi};
laddr <= laddr + 24'd1;
rclk <= 1'b1;
bit_cnt <= bit_cnt + 1'd1;
if(bit_cnt != 7)
sbuf[6:0] <= { sbuf[5:0], SPI_DI };
// finished reading a byte, prepare to transfer to clk_sys
if(bit_cnt == 7) begin
spi_byte_in <= { sbuf, SPI_DI};
spi_receiver_strobe_r <= ~spi_receiver_strobe_r;
end
// expose file (menu) index
if((cmd == UIO_FILE_INDEX) && (cnt == 15))
index <= {sbuf[3:0], sdi};
end
end
reg rclkD, rclkD2;
always@(posedge clk) begin
// bring all signals from spi clock domain into local clock domain
rclkD <= rclk;
rclkD2 <= rclkD;
wr <= 1'b0;
if(rclkD && !rclkD2) begin
addr <= laddr;
data <= ldata;
wr <= 1'b1;
always @(posedge clk_sys) begin
reg spi_receiver_strobe;
reg spi_transfer_end;
reg spi_receiver_strobeD;
reg spi_transfer_endD;
reg [7:0] acmd;
reg [2:0] abyte_cnt; // counts bytes
reg [24:0] addr;
reg wr_int;
reg clkrefD;
reg hi;
//synchronize between SPI and sys clock domains
spi_receiver_strobeD <= spi_receiver_strobe_r;
spi_receiver_strobe <= spi_receiver_strobeD;
spi_transfer_endD <= spi_transfer_end_r;
spi_transfer_end <= spi_transfer_endD;
if (~spi_transfer_endD & spi_transfer_end) begin
abyte_cnt <= 3'd0;
end else if (spi_receiver_strobeD ^ spi_receiver_strobe) begin
if(~&abyte_cnt) abyte_cnt <= abyte_cnt + 1'd1;
if(abyte_cnt == 0) begin
acmd <= spi_byte_in;
hi <= 0;
end else begin
case (acmd)
UIO_FILE_TX: begin
// prepare
if(spi_byte_in) begin
addr <= 0;
ioctl_download <= 1;
end else begin
ioctl_addr <= addr;
ioctl_download <= 0;
end
end
// transfer
UIO_FILE_TX_DAT: begin
ioctl_addr <= addr;
if (hi) ioctl_dout[7:0] <= spi_byte_in; else ioctl_dout[15:8] <= spi_byte_in;
hi <= ~hi;
if (hi) wr_int <= 1;
end
// expose file (menu) index
UIO_FILE_INDEX: ioctl_index <= spi_byte_in;
endcase
end
end
ioctl_wr <= 0;
clkrefD <= ioctl_clkref;
if (wr_int & ~clkrefD & ioctl_clkref) begin
addr <= addr + 1'd1;
ioctl_wr <= 1;
wr_int <= 0;
end
end

View File

@@ -140,7 +140,7 @@ set_location_assignment PIN_43 -to SDRAM_CLK
set_global_assignment -name ENABLE_SIGNALTAP OFF
set_global_assignment -name USE_SIGNALTAP_FILE stp1.stp
set_global_assignment -name USE_SIGNALTAP_FILE output_files/clk.stp
set_global_assignment -name CYCLONEII_RESERVE_NCEO_AFTER_CONFIGURATION "USE AS REGULAR IO"
set_global_assignment -name RESERVE_DATA0_AFTER_CONFIGURATION "USE AS REGULAR IO"
set_global_assignment -name RESERVE_DATA1_AFTER_CONFIGURATION "USE AS REGULAR IO"
@@ -148,7 +148,7 @@ set_global_assignment -name RESERVE_FLASH_NCE_AFTER_CONFIGURATION "USE AS REGULA
set_global_assignment -name RESERVE_DCLK_AFTER_CONFIGURATION "USE AS REGULAR IO"
set_global_assignment -name OPTIMIZE_HOLD_TIMING "ALL PATHS"
set_global_assignment -name OPTIMIZE_MULTI_CORNER_TIMING ON
set_global_assignment -name FITTER_EFFORT "FAST FIT"
set_global_assignment -name FITTER_EFFORT "AUTO FIT"
set_global_assignment -name POWER_PRESET_COOLING_SOLUTION "23 MM HEAT SINK WITH 200 LFPM AIRFLOW"
set_global_assignment -name POWER_BOARD_THERMAL_MODEL "NONE (CONSERVATIVE)"
set_global_assignment -name PARTITION_NETLIST_TYPE SOURCE -section_id Top
@@ -157,12 +157,36 @@ set_global_assignment -name PARTITION_COLOR 16764057 -section_id Top
set_instance_assignment -name CURRENT_STRENGTH_NEW "MAXIMUM CURRENT" -to VGA_*
set_global_assignment -name VERILOG_INPUT_VERSION VERILOG_2001
set_global_assignment -name VERILOG_SHOW_LMF_MAPPING_MESSAGES OFF
set_global_assignment -name PHYSICAL_SYNTHESIS_COMBO_LOGIC ON
set_global_assignment -name PHYSICAL_SYNTHESIS_REGISTER_RETIMING ON
set_global_assignment -name PHYSICAL_SYNTHESIS_ASYNCHRONOUS_SIGNAL_PIPELINING ON
set_global_assignment -name PHYSICAL_SYNTHESIS_REGISTER_DUPLICATION ON
set_global_assignment -name PHYSICAL_SYNTHESIS_COMBO_LOGIC_FOR_AREA ON
set_global_assignment -name PHYSICAL_SYNTHESIS_MAP_LOGIC_TO_MEMORY_FOR_AREA ON
set_global_assignment -name TIMEQUEST_MULTICORNER_ANALYSIS ON
set_global_assignment -name PROJECT_OUTPUT_DIRECTORY output_files
set_global_assignment -name ENABLE_CONFIGURATION_PINS OFF
set_global_assignment -name ENABLE_NCE_PIN OFF
set_global_assignment -name ENABLE_BOOT_SEL_PIN OFF
set_global_assignment -name VERILOG_FILE gb_mist.v
set_global_assignment -name SYSTEMVERILOG_FILE sdram.sv
set_global_assignment -name VERILOG_FILE sigma_delta_dac.v
set_global_assignment -name VHDL_FILE gbc_snd.vhd
set_global_assignment -name VERILOG_FILE data_io.v
set_global_assignment -name VERILOG_FILE sdram.v
set_global_assignment -name VERILOG_FILE user_io.v
set_global_assignment -name VERILOG_FILE osd.v
set_global_assignment -name QIP_FILE pll.qip
set_global_assignment -name VERILOG_FILE gb.v
set_global_assignment -name VERILOG_FILE lcd.v
set_global_assignment -name QIP_FILE boot_rom.qip
set_global_assignment -name VERILOG_FILE video.v
set_global_assignment -name VERILOG_FILE sprites.v
set_global_assignment -name VERILOG_FILE sprite.v
set_global_assignment -name VERILOG_FILE sprite_sort.v
set_global_assignment -name VERILOG_FILE timer.v
set_global_assignment -name QIP_FILE iram.qip
set_global_assignment -name QIP_FILE vram.qip
set_global_assignment -name QIP_FILE zpram.qip
set_global_assignment -name VHDL_FILE t80/T80.vhd
set_global_assignment -name VHDL_FILE t80/Z80.vhd
set_global_assignment -name VHDL_FILE t80/T80_Reg.vhd
@@ -170,18 +194,4 @@ set_global_assignment -name VHDL_FILE t80/T80_Pack.vhd
set_global_assignment -name VHDL_FILE t80/T80_ALU.vhd
set_global_assignment -name VHDL_FILE t80/GBse.vhd
set_global_assignment -name VHDL_FILE t80/T80_MCode.vhd
set_global_assignment -name QIP_FILE pll.qip
set_global_assignment -name VERILOG_FILE gb.v
set_global_assignment -name VERILOG_FILE lcd.v
set_global_assignment -name QIP_FILE boot_rom.qip
set_global_assignment -name SIGNALTAP_FILE stp1.stp
set_global_assignment -name VERILOG_FILE gb_mist.v
set_global_assignment -name QIP_FILE vram.qip
set_global_assignment -name QIP_FILE iram.qip
set_global_assignment -name VERILOG_FILE video.v
set_global_assignment -name QIP_FILE zpram.qip
set_global_assignment -name VERILOG_FILE sprites.v
set_global_assignment -name VERILOG_FILE sprite.v
set_global_assignment -name VERILOG_FILE sprite_sort.v
set_global_assignment -name VERILOG_FILE timer.v
set_instance_assignment -name PARTITION_HIERARCHY root_partition -to | -section_id Top

118
cores/gameboy/gb.sdc Normal file
View File

@@ -0,0 +1,118 @@
## Generated SDC file "hello_led.out.sdc"
## Copyright (C) 1991-2011 Altera Corporation
## Your use of Altera Corporation's design tools, logic functions
## and other software and tools, and its AMPP partner logic
## functions, and any output files from any of the foregoing
## (including device programming or simulation files), and any
## associated documentation or information are expressly subject
## to the terms and conditions of the Altera Program License
## Subscription Agreement, Altera MegaCore Function License
## Agreement, or other applicable license agreement, including,
## without limitation, that your use is for the sole purpose of
## programming logic devices manufactured by Altera and sold by
## Altera or its authorized distributors. Please refer to the
## applicable agreement for further details.
## VENDOR "Altera"
## PROGRAM "Quartus II"
## VERSION "Version 11.1 Build 216 11/23/2011 Service Pack 1 SJ Web Edition"
## DATE "Fri Jul 06 23:05:47 2012"
##
## DEVICE "EP3C25Q240C8"
##
#**************************************************************
# Time Information
#**************************************************************
set_time_format -unit ns -decimal_places 3
#**************************************************************
# Create Clock
#**************************************************************
create_clock -name clk_27 -period 37.037 [get_ports {CLOCK_27[0]}]
create_clock -name {SPI_SCK} -period 41.666 -waveform { 20.8 41.666 } [get_ports {SPI_SCK}]
#**************************************************************
# Create Generated Clock
#**************************************************************
derive_pll_clocks
#**************************************************************
# Set Clock Latency
#**************************************************************
#**************************************************************
# Set Clock Uncertainty
#**************************************************************
derive_clock_uncertainty;
#**************************************************************
# Set Input Delay
#**************************************************************
set_input_delay -clock [get_clocks {pll|altpll_component|auto_generated|pll1|clk[0]}] -max 6.4 [get_ports SDRAM_DQ[*]]
set_input_delay -clock [get_clocks {pll|altpll_component|auto_generated|pll1|clk[0]}] -min 3.2 [get_ports SDRAM_DQ[*]]
#**************************************************************
# Set Output Delay
#**************************************************************
set_output_delay -clock [get_clocks {pll|altpll_component|auto_generated|pll1|clk[0]}] -max 1.5 [get_ports {SDRAM_D* SDRAM_A* SDRAM_BA* SDRAM_n* SDRAM_CKE}]
set_output_delay -clock [get_clocks {pll|altpll_component|auto_generated|pll1|clk[0]}] -min -0.8 [get_ports {SDRAM_D* SDRAM_A* SDRAM_BA* SDRAM_n* SDRAM_CKE}]
set_output_delay -clock [get_clocks {pll|altpll_component|auto_generated|pll1|clk[0]}] -max 1.5 [get_ports SDRAM_CLK]
set_output_delay -clock [get_clocks {pll|altpll_component|auto_generated|pll1|clk[0]}] -min -0.8 [get_ports SDRAM_CLK]
set_output_delay -clock [get_clocks {pll|altpll_component|auto_generated|pll1|clk[0]}] -max 0 [get_ports {VGA_*}]
set_output_delay -clock [get_clocks {pll|altpll_component|auto_generated|pll1|clk[0]}] -min -5 [get_ports {VGA_*}]
#**************************************************************
# Set Clock Groups
#**************************************************************
set_clock_groups -asynchronous -group [get_clocks {SPI_SCK}] -group [get_clocks {pll|*}]
#**************************************************************
# Set False Path
#**************************************************************
#set_false_path -to [get_ports {VGA_*}]
set_false_path -to [get_ports {UART_TX}]
set_false_path -to [get_ports {AUDIO_L}]
set_false_path -to [get_ports {AUDIO_R}]
set_false_path -to [get_ports {LED}]
#**************************************************************
# Set Multicycle Path
#**************************************************************
#set_multicycle_path -to {VGA_*[*]} -setup 2
#set_multicycle_path -to {VGA_*[*]} -hold 1
#**************************************************************
# Set Maximum Delay
#**************************************************************
#**************************************************************
# Set Minimum Delay
#**************************************************************
#**************************************************************
# Set Input Transition
#**************************************************************

View File

@@ -58,7 +58,7 @@ module gb_mist (
output [5:0] VGA_B
);
assign LED = 1'b0; // light led
assign LED = ~dio_download;
// mix both joysticks to allow the user to use any
wire [7:0] joystick = joystick_0 | joystick_1;
@@ -83,22 +83,22 @@ wire [1:0] buttons;
// include user_io module for arm controller communication
user_io #(.STRLEN(CONF_STR_LEN)) user_io (
.conf_str ( CONF_STR ),
.clk_sys ( clk64 ),
.SPI_CLK ( SPI_SCK ),
.SPI_SS_IO ( CONF_DATA0 ),
.SPI_MISO ( SPI_DO ),
.SPI_MOSI ( SPI_DI ),
.status ( status ),
.buttons ( buttons ),
.buttons ( buttons ),
.joystick_0 ( joystick_0 ),
.joystick_1 ( joystick_1 )
);
wire reset = (reset_cnt != 0);
reg [9:0] reset_cnt;
always @(posedge clk4) begin
always @(posedge clk64) begin
if(status[0] || status[3] || buttons[1] || !pll_locked || dio_download)
reset_cnt <= 10'd1023;
else
@@ -120,8 +120,8 @@ sdram sdram (
.sd_cas ( SDRAM_nCAS ),
// system interface
.clk ( clk32 ),
.clkref ( clk4 ),
.clk ( clk64 ),
.sync ( clk8 ),
.init ( !pll_locked ),
// cpu interface
@@ -187,7 +187,7 @@ reg mbc1_ram_enable;
reg mbc1_mode;
reg [4:0] mbc1_rom_bank_reg;
reg [1:0] mbc1_ram_bank_reg;
always @(posedge clk4) begin
always @(posedge clk64) begin
if(reset) begin
mbc1_rom_bank_reg <= 5'd1;
mbc1_ram_bank_reg <= 2'd0;
@@ -244,7 +244,7 @@ wire [8:0] mbc_bank =
mbc1?mbc1_addr: // MBC1, 16k bank 0, 16k bank 1-127 + ram
{7'b0000000, cart_addr[14:13]}; // no MBC, 32k linear address
always @(posedge clk4) begin
always @(posedge clk64) begin
if(!pll_locked) begin
cart_mbc_type <= 8'h00;
cart_rom_size <= 8'h00;
@@ -262,18 +262,19 @@ end
// include ROM download helper
data_io data_io (
.clk_sys ( clk64 ),
// io controller spi interface
.sck ( SPI_SCK ),
.ss ( SPI_SS2 ),
.sdi ( SPI_DI ),
.SPI_SCK ( SPI_SCK ),
.SPI_SS2 ( SPI_SS2 ),
.SPI_DI ( SPI_DI ),
.ioctl_download ( dio_download ), // signal indicating an active rom download
.downloading ( dio_download ), // signal indicating an active rom download
// external ram interface
.clk ( clk4 ),
.wr ( dio_write ),
.addr ( dio_addr ),
.data ( dio_data )
.ioctl_clkref ( clk8 ),
.ioctl_wr ( dio_write ),
.ioctl_addr ( dio_addr ),
.ioctl_dout ( dio_data )
);
// select appropriate byte from 16 bit word returned by cart
@@ -294,10 +295,10 @@ wire [15:0] audio_right;
// the gameboy itself
gb gb (
.reset ( reset ),
.clk ( clk4 ), // the whole gameboy runs on 4mhnz
.clk ( clk4 ), // the whole gameboy runs on 4mhnz
.fast_boot ( status[2] ),
.joystick ( joystick ),
.fast_boot ( status[2] ),
.joystick ( joystick ),
// interface to the "external" game cartridge
.cart_addr ( cart_addr ),
@@ -309,7 +310,7 @@ gb gb (
// audio
.audio_l ( audio_left ),
.audio_r ( audio_right ),
// interface to the lcd
.lcd_clkena ( lcd_clkena ),
.lcd_data ( lcd_data ),
@@ -318,11 +319,11 @@ gb gb (
);
sigma_delta_dac dac (
.clk ( clk32 ),
.clk ( clk64 ),
.ldatasum ( audio_left[15:1] ),
.rdatasum ( audio_right[15:1] ),
.left ( AUDIO_L ),
.right ( AUDIO_R )
.left ( AUDIO_L ),
.right ( AUDIO_R )
);
// the lcd to vga converter
@@ -330,7 +331,8 @@ wire [5:0] video_r, video_g, video_b;
wire video_hs, video_vs;
lcd lcd (
.pclk ( clk8 ),
.clk64 ( clk64 ),
.pclk_en( ce_pix ),
.clk ( clk4 ),
.tint ( status[1] ),
@@ -350,46 +352,46 @@ lcd lcd (
// include the on screen display
osd #(16,0,4) osd (
.pclk ( clk32 ),
.clk_sys ( clk64 ),
// spi for OSD
.sdi ( SPI_DI ),
.sck ( SPI_SCK ),
.ss ( SPI_SS3 ),
.SPI_DI ( SPI_DI ),
.SPI_SCK ( SPI_SCK ),
.SPI_SS3 ( SPI_SS3 ),
.red_in ( video_r ),
.green_in ( video_g ),
.blue_in ( video_b ),
.hs_in ( video_hs ),
.vs_in ( video_vs ),
.R_in ( video_r ),
.G_in ( video_g ),
.B_in ( video_b ),
.HSync ( video_hs ),
.VSync ( video_vs ),
.red_out ( VGA_R ),
.green_out ( VGA_G ),
.blue_out ( VGA_B ),
.hs_out ( VGA_HS ),
.vs_out ( VGA_VS )
.R_out ( VGA_R ),
.G_out ( VGA_G ),
.B_out ( VGA_B )
);
reg clk4; // 4.194304 MHz CPU clock and GB pixel clock
always @(posedge clk8)
clk4 <= !clk4;
reg clk8; // 8.388608 MHz VGA pixel clock
always @(posedge clk16)
clk8 <= !clk8;
assign VGA_HS = video_hs;
assign VGA_VS = video_vs;
wire clk4 = ce_cpu;
wire clk8 = ce_pix;
reg ce_pix, ce_cpu;
always @(posedge clk64) begin
reg [3:0] div = 0;
div <= div + 1'd1;
ce_pix <= !div[2:0];
ce_cpu <= !div[3:0];
end
reg clk16; // 16.777216 MHz
always @(posedge clk32)
clk16 <= !clk16;
// 32 Mhz SDRAM clk
wire pll_locked;
wire clk32;
wire clk64;
pll pll (
.inclk0(CLOCK_27[0]),
.c0(clk32), // 2*16.777216 MHz
.c1(SDRAM_CLK), // same phase shifted
.c0(clk64), // 4*16.777216 MHz
.locked(pll_locked)
);
assign SDRAM_CLK = ~clk64;
endmodule

View File

@@ -4,6 +4,7 @@
// The gameboy lcd runs from a shift register which is filled at 4194304 pixels/sec
module lcd (
input clk64,
input clk,
input clkena,
input [1:0] data,
@@ -12,7 +13,7 @@ module lcd (
input tint,
// pixel clock
input pclk,
input pclk_en,
input on,
// VGA output
@@ -50,7 +51,7 @@ always @(posedge clk) begin
p_toggle <= !p_toggle;
end
end
//
parameter H = 160; // width of visible area
parameter HFP = 24; // unused time before hsync
@@ -69,7 +70,8 @@ reg[9:0] v_cnt; // vertical pixel counter
// horizontal pixel counter
reg [1:0] last_mode_h;
always@(posedge pclk) begin
always@(posedge clk64) begin
if (pclk_en) begin
last_mode_h <= mode;
if(h_cnt==H+HFP+HS+HBP-1) h_cnt <= 0;
@@ -83,11 +85,13 @@ always@(posedge pclk) begin
// end of hblank
if((mode == 2'b10) && (last_mode_h == 2'b00))
h_cnt <= 0;
end
end
// veritical pixel counter
reg [1:0] last_mode_v;
always@(posedge pclk) begin
always@(posedge clk64) begin
if (pclk_en) begin
// the vertical counter is processed at the begin of each hsync
if(h_cnt == H+HFP+HS+HBP-1) begin
if(v_cnt==VS+VFP+V+VBP-1) v_cnt <= 0;
@@ -105,6 +109,7 @@ always@(posedge pclk) begin
if((mode != 2'b01) && (last_mode_v == 2'b01))
v_cnt <= 616-4;
end
end
end
// -------------------------------------------------------------------------------
@@ -114,7 +119,8 @@ reg blank;
reg [1:0] pixel_reg;
reg [7:0] shift_reg_rptr;
always@(posedge pclk) begin
always@(posedge clk64) begin
if (pclk_en) begin
// visible area?
if((v_cnt < V) && (h_cnt < H)) begin
blank <= 1'b0;
@@ -124,6 +130,7 @@ always@(posedge pclk) begin
blank <= 1'b1;
shift_reg_rptr <= 8'd0;
end
end
end
wire [1:0] pixel = on?pixel_reg:2'b00;

View File

@@ -4,34 +4,32 @@
module osd (
// OSDs pixel clock, should be synchronous to cores pixel clock to
// avoid jitter.
input pclk,
input clk_sys,
// SPI interface
input sck,
input ss,
input sdi,
input SPI_SCK,
input SPI_SS3,
input SPI_DI,
// VGA signals coming from core
input [5:0] red_in,
input [5:0] green_in,
input [5:0] blue_in,
input hs_in,
input vs_in,
input [5:0] R_in,
input [5:0] G_in,
input [5:0] B_in,
input HSync,
input VSync,
// VGA signals going to video connector
output [5:0] red_out,
output [5:0] green_out,
output [5:0] blue_out,
output hs_out,
output vs_out
output [5:0] R_out,
output [5:0] G_out,
output [5:0] B_out
);
parameter OSD_X_OFFSET = 10'd0;
parameter OSD_Y_OFFSET = 10'd0;
parameter OSD_COLOR = 3'd0;
localparam OSD_WIDTH = 10'd256;
localparam OSD_HEIGHT = 10'd128;
localparam OSD_WIDTH = 10'd256;
localparam OSD_HEIGHT = 10'd128;
// *********************************************************************************
// spi client
@@ -39,45 +37,42 @@ localparam OSD_HEIGHT = 10'd128;
// this core supports only the display related OSD commands
// of the minimig
reg [7:0] sbuf;
reg [7:0] cmd;
reg [4:0] cnt;
reg [10:0] bcnt;
reg osd_enable;
reg [7:0] osd_buffer [2047:0]; // the OSD buffer itself
reg osd_enable;
(* ramstyle = "no_rw_check" *) reg [7:0] osd_buffer[2047:0]; // the OSD buffer itself
// the OSD has its own SPI interface to the io controller
always@(posedge sck, posedge ss) begin
if(ss == 1'b1) begin
cnt <= 5'd0;
bcnt <= 11'd0;
end else begin
sbuf <= { sbuf[6:0], sdi};
always@(posedge SPI_SCK, posedge SPI_SS3) begin
reg [4:0] cnt;
reg [10:0] bcnt;
reg [7:0] sbuf;
reg [7:0] cmd;
// 0:7 is command, rest payload
if(cnt < 15)
cnt <= cnt + 4'd1;
else
cnt <= 4'd8;
if(SPI_SS3) begin
cnt <= 0;
bcnt <= 0;
end else begin
sbuf <= {sbuf[6:0], SPI_DI};
if(cnt == 7) begin
cmd <= {sbuf[6:0], sdi};
// lower three command bits are line address
bcnt <= { sbuf[1:0], sdi, 8'h00};
// 0:7 is command, rest payload
if(cnt < 15) cnt <= cnt + 1'd1;
else cnt <= 8;
// command 0x40: OSDCMDENABLE, OSDCMDDISABLE
if(sbuf[6:3] == 4'b0100)
osd_enable <= sdi;
end
if(cnt == 7) begin
cmd <= {sbuf[6:0], SPI_DI};
// command 0x20: OSDCMDWRITE
if((cmd[7:3] == 5'b00100) && (cnt == 15)) begin
osd_buffer[bcnt] <= {sbuf[6:0], sdi};
bcnt <= bcnt + 11'd1;
end
end
// lower three command bits are line address
bcnt <= {sbuf[1:0], SPI_DI, 8'h00};
// command 0x40: OSDCMDENABLE, OSDCMDDISABLE
if(sbuf[6:3] == 4'b0100) osd_enable <= SPI_DI;
end
// command 0x20: OSDCMDWRITE
if((cmd[7:3] == 5'b00100) && (cnt == 15)) begin
osd_buffer[bcnt] <= {sbuf[6:0], SPI_DI};
bcnt <= bcnt + 1'd1;
end
end
end
// *********************************************************************************
@@ -85,98 +80,100 @@ end
// *********************************************************************************
// horizontal counter
reg [9:0] h_cnt;
reg hsD, hsD2;
reg [9:0] hs_low, hs_high;
wire hs_pol = hs_high < hs_low;
wire [9:0] h_dsp_width = hs_pol?hs_low:hs_high;
wire [9:0] h_dsp_ctr = { 1'b0, h_dsp_width[9:1] };
always @(posedge pclk) begin
// bring hsync into local clock domain
hsD <= hs_in;
hsD2 <= hsD;
// falling edge of hs_in
if(!hsD && hsD2) begin
h_cnt <= 10'd0;
hs_high <= h_cnt;
end
// rising edge of hs_in
else if(hsD && !hsD2) begin
h_cnt <= 10'd0;
hs_low <= h_cnt;
end
else
h_cnt <= h_cnt + 10'd1;
end
reg [9:0] h_cnt;
reg [9:0] hs_low, hs_high;
wire hs_pol = hs_high < hs_low;
wire [9:0] dsp_width = hs_pol ? hs_low : hs_high;
// vertical counter
reg [9:0] v_cnt;
reg vsD, vsD2;
reg [9:0] vs_low, vs_high;
wire vs_pol = vs_high < vs_low;
wire [9:0] v_dsp_width = vs_pol?vs_low:vs_high;
wire [9:0] v_dsp_ctr = { 1'b0, v_dsp_width[9:1] };
reg [9:0] v_cnt;
reg [9:0] vs_low, vs_high;
wire vs_pol = vs_high < vs_low;
wire [9:0] dsp_height = vs_pol ? vs_low : vs_high;
always @(posedge hs_in) begin
// bring vsync into local clock domain
vsD <= vs_in;
vsD2 <= vsD;
wire doublescan = (dsp_height>350);
// falling edge of vs_in
if(!vsD && vsD2) begin
v_cnt <= 10'd0;
vs_high <= v_cnt;
reg ce_pix;
always @(negedge clk_sys) begin
integer cnt = 0;
integer pixsz, pixcnt;
reg hs;
cnt <= cnt + 1;
hs <= HSync;
pixcnt <= pixcnt + 1;
if(pixcnt == pixsz) pixcnt <= 0;
ce_pix <= !pixcnt;
if(hs && ~HSync) begin
cnt <= 0;
pixsz <= (cnt >> 9) - 1;
pixcnt <= 0;
ce_pix <= 1;
end
end
// rising edge of vs_in
else if(vsD && !vsD2) begin
v_cnt <= 10'd0;
vs_low <= v_cnt;
end
else
v_cnt <= v_cnt + 10'd1;
always @(posedge clk_sys) begin
reg hsD, hsD2;
reg vsD, vsD2;
if(ce_pix) begin
// bring hsync into local clock domain
hsD <= HSync;
hsD2 <= hsD;
// falling edge of HSync
if(!hsD && hsD2) begin
h_cnt <= 0;
hs_high <= h_cnt;
end
// rising edge of HSync
else if(hsD && !hsD2) begin
h_cnt <= 0;
hs_low <= h_cnt;
v_cnt <= v_cnt + 1'd1;
end else begin
h_cnt <= h_cnt + 1'd1;
end
vsD <= VSync;
vsD2 <= vsD;
// falling edge of VSync
if(!vsD && vsD2) begin
v_cnt <= 0;
vs_high <= v_cnt;
end
// rising edge of VSync
else if(vsD && !vsD2) begin
v_cnt <= 0;
vs_low <= v_cnt;
end
end
end
// area in which OSD is being displayed
wire [9:0] h_osd_start = h_dsp_ctr + OSD_X_OFFSET - (OSD_WIDTH >> 1);
wire [9:0] h_osd_end = h_dsp_ctr + OSD_X_OFFSET + (OSD_WIDTH >> 1) - 1;
wire [9:0] v_osd_start = v_dsp_ctr + OSD_Y_OFFSET - (OSD_HEIGHT >> 1);
wire [9:0] v_osd_end = v_dsp_ctr + OSD_Y_OFFSET + (OSD_HEIGHT >> 1) - 1;
wire [9:0] h_osd_start = ((dsp_width - OSD_WIDTH)>> 1) + OSD_X_OFFSET;
wire [9:0] h_osd_end = h_osd_start + OSD_WIDTH;
wire [9:0] v_osd_start = ((dsp_height- (OSD_HEIGHT<<doublescan))>> 1) + OSD_Y_OFFSET;
wire [9:0] v_osd_end = v_osd_start + (OSD_HEIGHT<<doublescan);
wire [9:0] osd_hcnt = h_cnt - h_osd_start + 1'd1; // one pixel offset for osd_byte register
wire [9:0] osd_vcnt = v_cnt - v_osd_start;
reg h_osd_active, v_osd_active;
always @(posedge pclk) begin
if(hs_in != hs_pol) begin
if(h_cnt == h_osd_start) h_osd_active <= 1'b1;
if(h_cnt == h_osd_end) h_osd_active <= 1'b0;
end
if(vs_in != vs_pol) begin
if(v_cnt == v_osd_start) v_osd_active <= 1'b1;
if(v_cnt == v_osd_end) v_osd_active <= 1'b0;
end
end
wire osd_de = osd_enable &&
(HSync != hs_pol) && (h_cnt >= h_osd_start) && (h_cnt < h_osd_end) &&
(VSync != vs_pol) && (v_cnt >= v_osd_start) && (v_cnt < v_osd_end);
wire osd_de = osd_enable && h_osd_active && v_osd_active;
reg [7:0] osd_byte;
always @(posedge clk_sys) if(ce_pix) osd_byte <= osd_buffer[{doublescan ? osd_vcnt[7:5] : osd_vcnt[6:4], osd_hcnt[7:0]}];
wire [7:0] osd_hcnt = h_cnt - h_osd_start + 7'd1; // one pixel offset for osd_byte register
wire [6:0] osd_vcnt = v_cnt - v_osd_start;
wire osd_pixel = osd_byte[doublescan ? osd_vcnt[4:2] : osd_vcnt[3:1]];
wire osd_pixel = osd_byte[osd_vcnt[3:1]];
assign R_out = !osd_de ? R_in : {osd_pixel, osd_pixel, OSD_COLOR[2], R_in[5:3]};
assign G_out = !osd_de ? G_in : {osd_pixel, osd_pixel, OSD_COLOR[1], G_in[5:3]};
assign B_out = !osd_de ? B_in : {osd_pixel, osd_pixel, OSD_COLOR[0], B_in[5:3]};
reg [7:0] osd_byte;
always @(posedge pclk)
osd_byte <= osd_buffer[{osd_vcnt[6:4], osd_hcnt}];
wire [2:0] osd_color = OSD_COLOR;
assign red_out = !osd_de?red_in: {osd_pixel, osd_pixel, osd_color[2], red_in[5:3] };
assign green_out = !osd_de?green_in:{osd_pixel, osd_pixel, osd_color[1], green_in[5:3]};
assign blue_out = !osd_de?blue_in: {osd_pixel, osd_pixel, osd_color[0], blue_in[5:3] };
assign hs_out = hs_in;
assign vs_out = vs_in;
endmodule
endmodule

11
cores/gameboy/pll.ppf Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE pinplan>
<pinplan intended_family="Cyclone III" variation_name="pll" megafunction_name="ALTPLL" specifies="all_ports">
<global>
<pin name="inclk0" direction="input" scope="external" source="clock" />
<pin name="c0" direction="output" scope="external" source="clock" />
<pin name="c1" direction="output" scope="external" source="clock" />
<pin name="locked" direction="output" scope="external" />
</global>
</pinplan>

View File

@@ -98,7 +98,7 @@ module pll (
.vcounderrange ());
defparam
altpll_component.bandwidth_type = "AUTO",
altpll_component.clk0_divide_by = 70,
altpll_component.clk0_divide_by = 35,
altpll_component.clk0_duty_cycle = 50,
altpll_component.clk0_multiply_by = 87,
altpll_component.clk0_phase_shift = "0",
@@ -179,11 +179,11 @@ endmodule
// Retrieval info: PRIVATE: CUR_DEDICATED_CLK STRING "c0"
// Retrieval info: PRIVATE: CUR_FBIN_CLK STRING "c0"
// Retrieval info: PRIVATE: DEVICE_SPEED_GRADE STRING "8"
// Retrieval info: PRIVATE: DIV_FACTOR0 NUMERIC "70"
// Retrieval info: PRIVATE: DIV_FACTOR0 NUMERIC "35"
// Retrieval info: PRIVATE: DIV_FACTOR1 NUMERIC "70"
// Retrieval info: PRIVATE: DUTY_CYCLE0 STRING "50.00000000"
// Retrieval info: PRIVATE: DUTY_CYCLE1 STRING "50.00000000"
// Retrieval info: PRIVATE: EFF_OUTPUT_FREQ_VALUE0 STRING "33.557144"
// Retrieval info: PRIVATE: EFF_OUTPUT_FREQ_VALUE0 STRING "67.114288"
// Retrieval info: PRIVATE: EFF_OUTPUT_FREQ_VALUE1 STRING "33.557144"
// Retrieval info: PRIVATE: EXPLICIT_SWITCHOVER_COUNTER STRING "0"
// Retrieval info: PRIVATE: EXT_FEEDBACK_RADIO STRING "0"
@@ -259,7 +259,7 @@ endmodule
// Retrieval info: PRIVATE: ZERO_DELAY_RADIO STRING "0"
// Retrieval info: LIBRARY: altera_mf altera_mf.altera_mf_components.all
// Retrieval info: CONSTANT: BANDWIDTH_TYPE STRING "AUTO"
// Retrieval info: CONSTANT: CLK0_DIVIDE_BY NUMERIC "70"
// Retrieval info: CONSTANT: CLK0_DIVIDE_BY NUMERIC "35"
// Retrieval info: CONSTANT: CLK0_DUTY_CYCLE NUMERIC "50"
// Retrieval info: CONSTANT: CLK0_MULTIPLY_BY NUMERIC "87"
// Retrieval info: CONSTANT: CLK0_PHASE_SHIFT STRING "0"

View File

@@ -1,10 +1,9 @@
//
// sdram.v
//
// sdram controller implementation for the MiST board
// https://github.com/mist-devel
// sdram controller implementation for the MiST/MiSTer boards
//
// Copyright (c) 2015 Till Harbaum <till@harbaum.org>
// Copyright (c) 2017 Sorgelig
//
// 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
@@ -20,13 +19,21 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
module sdram (
//
//
// This SDRAM module provides/writes the data in 8 cycles of clock.
// So, with 64MHz of system clock, it can emulate 8MHz asynchronous DRAM.
//
//
module sdram
(
// interface to the MT48LC16M16 chip
inout [15:0] sd_data, // 16 bit bidirectional data bus
output reg [12:0] sd_addr, // 13 bit multiplexed address bus
output reg [1:0] sd_dqm, // two byte masks
output reg[1:0] sd_ba, // two banks
output reg [1:0] sd_ba, // two banks
output sd_cs, // a single chip select
output sd_we, // write enable
output sd_ras, // row address select
@@ -34,57 +41,51 @@ module sdram (
// cpu/chipset interface
input init, // init signal after FPGA config to initialize RAM
input clk, // sdram is accessed at up to 128MHz
input clkref, // reference clock to sync to
input [15:0] din, // data input from chipset/cpu
output [15:0] dout, // data output to chipset/cpu
input [23:0] addr, // 24 bit word address
input [1:0] ds, // data strobe for hi/low byte
input clk, // sdram is accessed at 64MHz
input sync,
input [15:0] din, // data input from chipset/cpu
output reg [15:0] dout, // data output to chipset/cpu
input [23:0] addr, // 24 bit word address
input [1:0] ds, // upper/lower data strobe
input oe, // cpu/chipset requests read
input we // cpu/chipset requests write
);
// no burst configured
localparam RASCAS_DELAY = 3'd3; // tRCD>=20ns -> 2 cycles@64MHz
localparam BURST_LENGTH = 3'b000; // 000=none, 001=2, 010=4, 011=8
localparam RASCAS_DELAY = 3'd2; // tRCD=20ns -> 3 cycles@128MHz
localparam BURST_LENGTH = 3'b000; // 000=1, 001=2, 010=4, 011=8
localparam ACCESS_TYPE = 1'b0; // 0=sequential, 1=interleaved
localparam CAS_LATENCY = 3'd3; // 2/3 allowed
localparam CAS_LATENCY = 3'd2; // 2/3 allowed
localparam OP_MODE = 2'b00; // only 00 (standard operation) allowed
localparam NO_WRITE_BURST = 1'b1; // 0= write burst enabled, 1=only single access write
localparam MODE = { 3'b000, NO_WRITE_BURST, OP_MODE, CAS_LATENCY, ACCESS_TYPE, BURST_LENGTH};
// ---------------------------------------------------------------------
// ------------------------ cycle state machine ------------------------
// ---------------------------------------------------------------------
localparam STATE_IDLE = 3'd0; // first state in cycle
localparam STATE_CMD_START = 3'd1; // state in which a new command can be started
localparam STATE_CMD_CONT = STATE_CMD_START + RASCAS_DELAY - 3'd1; // 4 command can be continued
localparam STATE_LAST = 3'd7; // last state in cycle
// The state machine runs at 128Mhz synchronous to the 8 Mhz chipset clock.
// It wraps from T15 to T0 on the rising edge of clk_8
localparam STATE_FIRST = 3'd0; // first state in cycle
localparam STATE_CMD_START = 3'd1; // state in which a new command can be started
localparam STATE_CMD_CONT = STATE_CMD_START + RASCAS_DELAY; // command can be continued
localparam STATE_READ = STATE_CMD_CONT + CAS_LATENCY + 4'd1;
localparam STATE_HIGHZ = STATE_READ - 4'd1; // disable output to prevent contention
reg [2:0] q;
always @(posedge clk) begin
// 32Mhz counter synchronous to 4 Mhz clock
// force counter to pass state 5->6 exactly after the rising edge of clkref
// since clkref is two clocks early
if(((q == 7) && ( clkref == 0)) ||
((q == 0) && ( clkref == 1)) ||
((q != 7) && (q != 0)))
q <= q + 3'd1;
end
// ---------------------------------------------------------------------
// --------------------------- startup/reset ---------------------------
// ---------------------------------------------------------------------
// wait 1ms (32 clkref cycles) after FPGA config is done before going
// wait 1ms (32 8Mhz cycles) after FPGA config is done before going
// into normal operation. Initialize the ram in the last 16 reset cycles (cycles 15-0)
reg [4:0] reset;
always @(posedge clk) begin
if(init) reset <= 5'h1f;
else if((q == STATE_LAST) && (reset != 0))
else if((stage == STATE_FIRST) && (reset != 0))
reset <= reset - 5'd1;
end
@@ -111,39 +112,82 @@ assign sd_ras = sd_cmd[2];
assign sd_cas = sd_cmd[1];
assign sd_we = sd_cmd[0];
assign sd_data = we?din:16'bZZZZZZZZZZZZZZZZ;
// drive ram data lines when writing, set them as inputs otherwise
assign sd_data = mode[1] ? din_r : 16'bZZZZZZZZZZZZZZZZ;
assign dout = sd_data;
reg [1:0] mode;
reg [15:0] din_r;
reg [2:0] stage;
always @(posedge clk) begin
sd_cmd <= CMD_INHIBIT;
reg [12:0] addr_r;
reg [1:0] ds_r;
reg old_sync;
if(|stage) stage <= stage + 1'd1;
old_sync <= sync;
if(~old_sync & sync) stage <= 1;
sd_cmd <= CMD_INHIBIT; // default: idle
if(reset != 0) begin
sd_ba <= 2'b00;
sd_dqm <= 2'b00;
if(reset == 13) sd_addr <= 13'b0010000000000;
else sd_addr <= MODE;
// initialization takes place at the end of the reset phase
if(stage == STATE_CMD_START) begin
if(q == STATE_IDLE) begin
if(reset == 13) sd_cmd <= CMD_PRECHARGE;
if(reset == 2) sd_cmd <= CMD_LOAD_MODE;
if(reset == 13) begin
sd_cmd <= CMD_PRECHARGE;
sd_addr[10] <= 1'b1; // precharge all banks
end
if(reset == 2) begin
sd_cmd <= CMD_LOAD_MODE;
sd_addr <= MODE;
end
end
mode <= 0;
sd_dqm <= 2'b11;
end else begin
if(q <= STATE_CMD_START) begin
sd_addr <= addr[20:8];
sd_ba <= addr[22:21];
sd_dqm <= { !ds[1], !ds[0] };
end else
sd_addr <= { 4'b0010, addr[23], addr[7:0]};
if(q == STATE_IDLE) begin
if(we || oe) sd_cmd <= CMD_ACTIVE;
else sd_cmd <= CMD_AUTO_REFRESH;
end else if(q == STATE_CMD_CONT) begin
if(we) sd_cmd <= CMD_WRITE;
else if(oe) sd_cmd <= CMD_READ;
end
// normal operation
if(stage == STATE_CMD_START) begin
if(we || oe) begin
mode <= {we, oe};
// RAS phase
sd_cmd <= CMD_ACTIVE;
sd_addr <= { 1'b0, addr[19:8] };
sd_ba <= addr[21:20];
ds_r <= ds;
din_r <= din;
addr_r <= { 4'b0010, addr[22], addr[7:0] }; // auto precharge
end
else begin
sd_cmd <= CMD_AUTO_REFRESH;
mode <= 0;
end
end
// CAS phase
if(stage == STATE_CMD_CONT && mode) begin
sd_cmd <= mode[1] ? CMD_WRITE : CMD_READ;
sd_addr <= addr_r;
if(mode[1]) sd_dqm <= ~ds_r;
else sd_dqm <= 2'b00;
end
if(stage == STATE_HIGHZ) begin
sd_dqm <= 2'b11; // disable chip output
mode[1] <= 0; // disable data output
end
if(stage == STATE_READ && mode) begin
dout <= sd_data;
end
end
end

View File

@@ -22,60 +22,75 @@
// parameter STRLEN and the actual length of conf_str have to match
module user_io #(parameter STRLEN=0) (
module user_io #(parameter STRLEN=0, parameter PS2DIV=100) (
input [(8*STRLEN)-1:0] conf_str,
input SPI_CLK,
input SPI_SS_IO,
output reg SPI_MISO,
input SPI_MOSI,
output reg [7:0] joystick_0,
output reg [7:0] joystick_1,
output reg [15:0] joystick_analog_0,
output reg [15:0] joystick_analog_1,
output [1:0] buttons,
output [1:0] switches,
input clk_sys, // clock for system-related messages (kbd, joy, etc...)
input clk_sd, // clock for SD-card related messages
output reg [7:0] status,
input SPI_CLK,
input SPI_SS_IO,
output reg SPI_MISO,
input SPI_MOSI,
output reg [31:0] joystick_0,
output reg [31:0] joystick_1,
output reg [31:0] joystick_2,
output reg [31:0] joystick_3,
output reg [31:0] joystick_4,
output reg [15:0] joystick_analog_0,
output reg [15:0] joystick_analog_1,
output [1:0] buttons,
output [1:0] switches,
output scandoubler_disable,
output ypbpr,
output reg [31:0] status,
// connection to sd card emulation
input [31:0] sd_lba,
input sd_rd,
input sd_wr,
output reg sd_ack,
input sd_conf,
input sd_sdhc,
output [7:0] sd_dout, // valid on rising edge of sd_dout_strobe
output reg sd_dout_strobe,
input [7:0] sd_din,
output reg sd_din_strobe,
input [31:0] sd_lba,
input sd_rd,
input sd_wr,
output reg sd_ack,
output reg sd_ack_conf,
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,
input [7:0] sd_din,
output reg sd_din_strobe,
output reg [8:0] sd_buff_addr,
// ps2 keyboard emulation
input ps2_clk, // 12-16khz provided by core
output ps2_kbd_clk,
output reg ps2_kbd_data,
output ps2_mouse_clk,
output reg ps2_mouse_data,
output reg img_mounted, //rising edge if a new image is mounted
output reg [31:0] img_size, // size of image in bytes
// ps2 keyboard/mouse emulation
output ps2_kbd_clk,
output reg ps2_kbd_data,
output ps2_mouse_clk,
output reg ps2_mouse_data,
// mouse data
output reg [7:0] mouse_x,
output reg [7:0] mouse_y,
output reg [7:0] mouse_flags, // YOvfl, XOvfl, dy8, dx8, 1, mbtn, rbtn, lbtn
output reg mouse_strobe, // mouse data is valid on mouse_strobe
// serial com port
input [7:0] serial_data,
input serial_strobe
input [7:0] serial_data,
input serial_strobe
);
reg [6:0] sbuf;
reg [7:0] cmd;
reg [2:0] bit_cnt; // counts bits 0-7 0-7 ...
reg [7:0] byte_cnt; // counts bytes
reg [5:0] joystick0;
reg [5:0] joystick1;
reg [3:0] but_sw;
reg [2:0] stick_idx;
reg [6:0] sbuf;
reg [7:0] cmd;
reg [2:0] bit_cnt; // counts bits 0-7 0-7 ...
reg [9:0] byte_cnt; // counts bytes
reg [7:0] but_sw;
reg [2:0] stick_idx;
assign buttons = but_sw[1:0];
assign switches = but_sw[3:2];
assign sd_dout = { sbuf, SPI_MOSI};
assign scandoubler_disable = but_sw[4];
assign ypbpr = but_sw[5];
// this variant of user_io is for 8 bit cores (type == a4) only
wire [7:0] core_type = 8'ha4;
@@ -83,63 +98,22 @@ wire [7:0] core_type = 8'ha4;
// command byte read by the io controller
wire [7:0] sd_cmd = { 4'h5, sd_conf, sd_sdhc, sd_wr, sd_rd };
// filter spi clock. the 8 bit gate delay is ~2.5ns in total
wire [7:0] spi_sck_D = { spi_sck_D[6:0], SPI_CLK } /* synthesis keep */;
wire spi_sck = (spi_sck && spi_sck_D != 8'h00) || (!spi_sck && spi_sck_D == 8'hff);
// drive MISO only when transmitting core id
always@(negedge spi_sck or posedge SPI_SS_IO) begin
if(SPI_SS_IO == 1) begin
SPI_MISO <= 1'bZ;
end else begin
// first byte returned is always core type, further bytes are
// command dependent
if(byte_cnt == 0) begin
SPI_MISO <= core_type[~bit_cnt];
end else begin
// reading serial fifo
if(cmd == 8'h1b) begin
// send alternating flag byte and data
if(byte_cnt[0]) SPI_MISO <= serial_out_status[~bit_cnt];
else SPI_MISO <= serial_out_byte[~bit_cnt];
end
// reading config string
else if(cmd == 8'h14) begin
// returning a byte from string
if(byte_cnt < STRLEN + 1)
SPI_MISO <= conf_str[{STRLEN - byte_cnt,~bit_cnt}];
else
SPI_MISO <= 1'b0;
end
// reading sd card status
else if(cmd == 8'h16) begin
if(byte_cnt == 1)
SPI_MISO <= sd_cmd[~bit_cnt];
else if((byte_cnt >= 2) && (byte_cnt < 6))
SPI_MISO <= sd_lba[{5-byte_cnt, ~bit_cnt}];
else
SPI_MISO <= 1'b0;
end
// reading sd card write data
else if(cmd == 8'h18)
SPI_MISO <= sd_din[~bit_cnt];
else
SPI_MISO <= 1'b0;
end
end
end
wire spi_sck = SPI_CLK;
// ---------------- PS2 ---------------------
// 8 byte fifos to store ps2 bytes
localparam PS2_FIFO_BITS = 3;
reg ps2_clk;
always @(negedge clk_sys) begin
integer cnt;
cnt <= cnt + 1'd1;
if(cnt == PS2DIV) begin
ps2_clk <= ~ps2_clk;
cnt <= 0;
end
end
// keyboard
reg [7:0] ps2_kbd_fifo [(2**PS2_FIFO_BITS)-1:0];
reg [PS2_FIFO_BITS-1:0] ps2_kbd_wptr;
@@ -155,56 +129,60 @@ assign ps2_kbd_clk = ps2_clk || (ps2_kbd_tx_state == 0);
// ps2 transmitter
// Takes a byte from the FIFO and sends it in a ps2 compliant serial format.
reg ps2_kbd_r_inc;
always@(posedge ps2_clk) begin
ps2_kbd_r_inc <= 1'b0;
if(ps2_kbd_r_inc)
ps2_kbd_rptr <= ps2_kbd_rptr + 1;
always@(posedge clk_sys) begin
reg ps2_clkD;
// transmitter is idle?
if(ps2_kbd_tx_state == 0) begin
// data in fifo present?
if(ps2_kbd_wptr != ps2_kbd_rptr) 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;
ps2_clkD <= ps2_clk;
if (~ps2_clkD & ps2_clk) begin
ps2_kbd_r_inc <= 1'b0;
// put start bit on data line
ps2_kbd_data <= 1'b0; // start bit is 0
if(ps2_kbd_r_inc)
ps2_kbd_rptr <= ps2_kbd_rptr + 1'd1;
// transmitter is idle?
if(ps2_kbd_tx_state == 0) begin
// data in fifo present?
if(ps2_kbd_wptr != ps2_kbd_rptr) 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 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
// mouse
reg [7:0] ps2_mouse_fifo [(2**PS2_FIFO_BITS)-1:0];
reg [PS2_FIFO_BITS-1:0] ps2_mouse_wptr;
@@ -220,53 +198,57 @@ assign ps2_mouse_clk = ps2_clk || (ps2_mouse_tx_state == 0);
// ps2 transmitter
// Takes a byte from the FIFO and sends it in a ps2 compliant serial format.
reg ps2_mouse_r_inc;
always@(posedge ps2_clk) begin
ps2_mouse_r_inc <= 1'b0;
if(ps2_mouse_r_inc)
ps2_mouse_rptr <= ps2_mouse_rptr + 1;
always@(posedge clk_sys) begin
reg ps2_clkD;
// transmitter is idle?
if(ps2_mouse_tx_state == 0) begin
// data in fifo present?
if(ps2_mouse_wptr != ps2_mouse_rptr) 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;
ps2_clkD <= ps2_clk;
if (~ps2_clkD & ps2_clk) begin
ps2_mouse_r_inc <= 1'b0;
// put start bit on data line
ps2_mouse_data <= 1'b0; // start bit is 0
if(ps2_mouse_r_inc)
ps2_mouse_rptr <= ps2_mouse_rptr + 1'd1;
// transmitter is idle?
if(ps2_mouse_tx_state == 0) begin
// data in fifo present?
if(ps2_mouse_wptr != ps2_mouse_rptr) 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 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
@@ -279,7 +261,7 @@ reg [SERIAL_OUT_FIFO_BITS-1:0] serial_out_wptr;
reg [SERIAL_OUT_FIFO_BITS-1:0] serial_out_rptr;
wire serial_out_data_available = serial_out_wptr != serial_out_rptr;
wire [7:0] serial_out_byte = serial_out_fifo[serial_out_rptr];
wire [7:0] serial_out_byte = serial_out_fifo[serial_out_rptr] /* synthesis keep */;
wire [7:0] serial_out_status = { 7'b1000000, serial_out_data_available};
// status[0] is reset signal from io controller and is thus used to flush
@@ -289,7 +271,7 @@ always @(posedge serial_strobe or posedge status[0]) begin
serial_out_wptr <= 0;
end else begin
serial_out_fifo[serial_out_wptr] <= serial_data;
serial_out_wptr <= serial_out_wptr + 1;
serial_out_wptr <= serial_out_wptr + 1'd1;
end
end
@@ -300,113 +282,268 @@ always@(negedge spi_sck or posedge status[0]) begin
if((byte_cnt != 0) && (cmd == 8'h1b)) begin
// read last bit -> advance read pointer
if((bit_cnt == 7) && !byte_cnt[0] && serial_out_data_available)
serial_out_rptr <= serial_out_rptr + 1;
serial_out_rptr <= serial_out_rptr + 1'd1;
end
end
end
// SPI receiver
// SPI bit and byte counters
always@(posedge spi_sck or posedge SPI_SS_IO) begin
if(SPI_SS_IO == 1) begin
bit_cnt <= 0;
byte_cnt <= 0;
end else begin
if((bit_cnt == 7)&&(~&byte_cnt))
byte_cnt <= byte_cnt + 8'd1;
bit_cnt <= bit_cnt + 1'd1;
end
end
// SPI transmitter FPGA -> IO
reg [7:0] spi_byte_out;
always@(negedge spi_sck or posedge SPI_SS_IO) begin
if(SPI_SS_IO == 1) begin
SPI_MISO <= 1'bZ;
end else begin
SPI_MISO <= spi_byte_out[~bit_cnt];
end
end
always@(posedge spi_sck or posedge SPI_SS_IO) begin
reg [31:0] sd_lba_r;
if(SPI_SS_IO == 1) begin
spi_byte_out <= core_type;
end else begin
// read the command byte to choose the response
if(bit_cnt == 7) begin
if(!byte_cnt) cmd <= {sbuf, SPI_MOSI};
spi_byte_out <= 0;
case({(!byte_cnt) ? {sbuf, SPI_MOSI} : cmd})
// reading config string
8'h14: if(byte_cnt < STRLEN) spi_byte_out <= conf_str[(STRLEN - byte_cnt - 1)<<3 +:8];
// reading sd card status
8'h16: if(byte_cnt == 0) begin
spi_byte_out <= sd_cmd;
sd_lba_r <= sd_lba;
end
else if(byte_cnt < 5) spi_byte_out <= sd_lba_r[(4-byte_cnt)<<3 +:8];
// reading sd card write data
8'h18: spi_byte_out <= sd_din;
8'h1b:
// send alternating flag byte and data
if(byte_cnt[0]) spi_byte_out <= serial_out_status;
else spi_byte_out <= serial_out_byte;
endcase
end
end
end
// SPI receiver IO -> FPGA
reg spi_receiver_strobe_r = 0;
reg spi_transfer_end_r = 1;
reg [7:0] spi_byte_in;
// Read at spi_sck clock domain, assemble bytes for transferring to clk_sys
always@(posedge spi_sck or posedge SPI_SS_IO) begin
if(SPI_SS_IO == 1) begin
bit_cnt <= 3'd0;
byte_cnt <= 8'd0;
sd_ack <= 1'b0;
sd_dout_strobe <= 1'b0;
sd_din_strobe <= 1'b0;
spi_transfer_end_r <= 1;
end else begin
sd_dout_strobe <= 1'b0;
sd_din_strobe <= 1'b0;
spi_transfer_end_r <= 0;
if(bit_cnt != 7)
sbuf[6:0] <= { sbuf[5:0], SPI_MOSI };
bit_cnt <= bit_cnt + 3'd1;
if((bit_cnt == 7)&&(byte_cnt != 8'd255))
byte_cnt <= byte_cnt + 8'd1;
// finished reading command byte
if(bit_cnt == 7) begin
if(byte_cnt == 0) begin
cmd <= { sbuf, SPI_MOSI};
// fetch first byte when sectore FPGA->IO command has been seen
if({ sbuf, SPI_MOSI} == 8'h18)
sd_din_strobe <= 1'b1;
if(({ sbuf, SPI_MOSI} == 8'h17) || ({ sbuf, SPI_MOSI} == 8'h18))
sd_ack <= 1'b1;
end else begin
// buttons and switches
if(cmd == 8'h01)
but_sw <= { sbuf[2:0], SPI_MOSI };
if(cmd == 8'h02)
joystick_0 <= { sbuf, SPI_MOSI };
if(cmd == 8'h03)
joystick_1 <= { sbuf, SPI_MOSI };
if(cmd == 8'h04) begin
// store incoming ps2 mouse bytes
ps2_mouse_fifo[ps2_mouse_wptr] <= { sbuf, SPI_MOSI };
ps2_mouse_wptr <= ps2_mouse_wptr + 1;
end
if(cmd == 8'h05) begin
// store incoming ps2 keyboard bytes
ps2_kbd_fifo[ps2_kbd_wptr] <= { sbuf, SPI_MOSI };
ps2_kbd_wptr <= ps2_kbd_wptr + 1;
end
if(cmd == 8'h15)
status <= { sbuf[6:0], SPI_MOSI };
// send sector IO -> FPGA
if(cmd == 8'h17) begin
// flag that download begins
// sd_dout <= { sbuf, SPI_MOSI};
sd_dout_strobe <= 1'b1;
end
// send sector FPGA -> IO
if(cmd == 8'h18)
sd_din_strobe <= 1'b1;
// send SD config IO -> FPGA
if(cmd == 8'h19) begin
// flag that download begins
// sd_dout <= { sbuf, SPI_MOSI};
// sd card knows data is config if sd_dout_strobe is asserted
// with sd_ack still being inactive (low)
sd_dout_strobe <= 1'b1;
end
// joystick analog
if(cmd == 8'h1a) begin
// first byte is joystick indes
if(byte_cnt == 1)
stick_idx <= { sbuf[1:0], SPI_MOSI };
else if(byte_cnt == 2) begin
// second byte is x axis
if(stick_idx == 0)
joystick_analog_0[15:8] <= { sbuf, SPI_MOSI };
else if(stick_idx == 1)
joystick_analog_1[15:8] <= { sbuf, SPI_MOSI };
end else if(byte_cnt == 3) begin
// third byte is y axis
if(stick_idx == 0)
joystick_analog_0[7:0] <= { sbuf, SPI_MOSI };
else if(stick_idx == 1)
joystick_analog_1[7:0] <= { sbuf, SPI_MOSI };
end
end
end
// finished reading a byte, prepare to transfer to clk_sys
if(bit_cnt == 7) begin
spi_byte_in <= { sbuf, SPI_MOSI};
spi_receiver_strobe_r <= ~spi_receiver_strobe_r;
end
end
end
// Process bytes from SPI at the clk_sys domain
always @(posedge clk_sys) begin
reg spi_receiver_strobe;
reg spi_transfer_end;
reg spi_receiver_strobeD;
reg spi_transfer_endD;
reg [7:0] acmd;
reg [7:0] abyte_cnt; // counts bytes
reg [7:0] mouse_flags_r;
reg [7:0] mouse_x_r;
//synchronize between SPI and sys clock domains
spi_receiver_strobeD <= spi_receiver_strobe_r;
spi_receiver_strobe <= spi_receiver_strobeD;
spi_transfer_endD <= spi_transfer_end_r;
spi_transfer_end <= spi_transfer_endD;
mouse_strobe <= 0;
if (~spi_transfer_endD & spi_transfer_end) begin
abyte_cnt <= 8'd0;
end else if (spi_receiver_strobeD ^ spi_receiver_strobe) begin
if(~&abyte_cnt)
abyte_cnt <= abyte_cnt + 8'd1;
if(abyte_cnt == 0) begin
acmd <= spi_byte_in;
end else begin
case(acmd)
// buttons and switches
8'h01: but_sw <= spi_byte_in;
8'h60: if (abyte_cnt < 5) joystick_0[(abyte_cnt-1)<<3 +:8] <= spi_byte_in;
8'h61: if (abyte_cnt < 5) joystick_1[(abyte_cnt-1)<<3 +:8] <= spi_byte_in;
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'h04: begin
// store incoming ps2 mouse bytes
ps2_mouse_fifo[ps2_mouse_wptr] <= spi_byte_in;
ps2_mouse_wptr <= ps2_mouse_wptr + 1'd1;
if (abyte_cnt == 1) mouse_flags_r <= spi_byte_in;
else if (abyte_cnt == 2) mouse_x_r <= spi_byte_in;
else if (abyte_cnt == 3) begin
mouse_flags <= mouse_flags_r;
mouse_x <= mouse_x_r;
mouse_y <= spi_byte_in;
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;
end
// joystick analog
8'h1a: begin
// first byte is joystick index
if(abyte_cnt == 1)
stick_idx <= spi_byte_in[2:0];
else if(abyte_cnt == 2) begin
// second byte is x axis
if(stick_idx == 0)
joystick_analog_0[15:8] <= spi_byte_in;
else if(stick_idx == 1)
joystick_analog_1[15:8] <= spi_byte_in;
end else if(abyte_cnt == 3) begin
// third byte is y axis
if(stick_idx == 0)
joystick_analog_0[7:0] <= spi_byte_in;
else if(stick_idx == 1)
joystick_analog_1[7:0] <= spi_byte_in;
end
end
8'h15: status <= spi_byte_in;
// status, 32bit version
8'h1e: if(abyte_cnt<5) status[(abyte_cnt-1)<<3 +:8] <= spi_byte_in;
endcase
end
end
end
// Process SD-card related bytes from SPI at the clk_sd domain
always @(posedge clk_sd) begin
reg spi_receiver_strobe;
reg spi_transfer_end;
reg spi_receiver_strobeD;
reg spi_transfer_endD;
reg sd_wrD;
reg [7:0] acmd;
reg [7:0] abyte_cnt; // counts bytes
//synchronize between SPI and sd clock domains
spi_receiver_strobeD <= spi_receiver_strobe_r;
spi_receiver_strobe <= spi_receiver_strobeD;
spi_transfer_endD <= spi_transfer_end_r;
spi_transfer_end <= spi_transfer_endD;
if(sd_dout_strobe) begin
sd_dout_strobe<= 0;
if(~&sd_buff_addr) sd_buff_addr <= sd_buff_addr + 1'b1;
end
sd_din_strobe<= 0;
sd_wrD <= sd_wr;
// fetch the first byte immediately after the write command seen
if (~sd_wrD & sd_wr) begin
sd_buff_addr <= 0;
sd_din_strobe <= 1;
end
img_mounted <= 0;
if (~spi_transfer_endD & spi_transfer_end) begin
abyte_cnt <= 8'd0;
sd_ack <= 1'b0;
sd_ack_conf <= 1'b0;
sd_dout_strobe <= 1'b0;
sd_din_strobe <= 1'b0;
sd_buff_addr <= 0;
end else if (spi_receiver_strobeD ^ spi_receiver_strobe) begin
if(~&abyte_cnt)
abyte_cnt <= abyte_cnt + 8'd1;
if(abyte_cnt == 0) begin
acmd <= spi_byte_in;
if(spi_byte_in == 8'h18) begin
sd_din_strobe <= 1'b1;
if(~&sd_buff_addr) sd_buff_addr <= sd_buff_addr + 1'b1;
end
if((spi_byte_in == 8'h17) || (spi_byte_in == 8'h18))
sd_ack <= 1'b1;
end else begin
case(acmd)
// send sector IO -> FPGA
8'h17: begin
// flag that download begins
sd_dout_strobe <= 1'b1;
sd_dout <= spi_byte_in;
end
// send sector FPGA -> IO
8'h18: begin
sd_din_strobe <= 1'b1;
if(~&sd_buff_addr) sd_buff_addr <= sd_buff_addr + 1'b1;
end
// send SD config IO -> FPGA
8'h19: begin
// flag that download begins
sd_dout_strobe <= 1'b1;
sd_ack_conf <= 1'b1;
sd_dout <= spi_byte_in;
end
8'h1c: img_mounted <= 1;
// send image info
8'h1d: if(abyte_cnt<5) img_size[(abyte_cnt-1)<<3 +:8] <= spi_byte_in;
endcase
end
end
end
endmodule