Initial commit.
This commit is contained in:
commit
8da567e4b6
395
vhdl/DRAM.vhd
Normal file
395
vhdl/DRAM.vhd
Normal file
@ -0,0 +1,395 @@
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
|
||||
--| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
|
||||
--| / __|/ _ \ / __|_ / _ \| | | | | |--
|
||||
--| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
|
||||
--| |___/\___/ \___/___\___/ \___/ | |--
|
||||
--| | http://sowerbutts.com/ |--
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| DRAM interface: Connect the CPU to the SDRAM on the Papilio Pro board. |--
|
||||
--| The SDRAM takes about 10 cycles to respond with data after making a |--
|
||||
--| request, so this module includes a direct-mapped cache to store |--
|
||||
--| recently read or written data in order to hide this latency. |--
|
||||
--+-------------------------------------------------------------------------+--
|
||||
--
|
||||
-- The Papilio Pro board has an 8MB SDRAM chip on the board. The socz80 MMU
|
||||
-- provides a 64MB (26-bit) phyiscal address space. The low 32MB of address space
|
||||
-- is allocated to the DRAM (the top 32MB being used for other memory devices).
|
||||
--
|
||||
-- The low 32MB is divided into two 16MB blocks. Accesses to the first block
|
||||
-- (starting at 0MB) go through the cache, while accesses to the second
|
||||
-- block (starting at 16MB) bypass the cache. There is only 8MB SDRAM on the
|
||||
-- Papilio Pro so it is aliased twice in each block, ie it appears at 0MB,
|
||||
-- 8MB, 16MB and 24MB.
|
||||
--
|
||||
-- The cache is direct mapped, ie the low bits of the address dictate which cache
|
||||
-- line to use and which byte within that line. When a cache line is written to
|
||||
-- the top bits of the address are stored in "cache tag" memory. When a cache line
|
||||
-- is read the top bits of the address are compared to the stored tag to determine
|
||||
-- if the cached data relates to the same address.
|
||||
--
|
||||
-- Each cache line consists of a 45 bits:
|
||||
-- 32 bits of cached data
|
||||
-- 4 validity bits to indicate if the cached data is valid or not
|
||||
-- 9 bits of address tag to indicate the top address bits of the
|
||||
--
|
||||
-- bit number (read these two 22222211111111110000000000
|
||||
-- lines top to bottom) 54321098765432109876543210
|
||||
--
|
||||
-- CPU address is 16 bits wide: PPPPOOOOOOOOOOOO (4 bit page, 12 bit offset)
|
||||
-- physical address is 26 bits wide: FFFFFFFFFFFFFFOOOOOOOOOOOO (14 bit frame, 12 bit offset)
|
||||
-- DRAM address is 25 bits wide: CIFFFFFFFFFFFOOOOOOOOOOOO (1 bit cache flag, 1 bit ignored, 11 bit frame, 12 bit offset)
|
||||
-- cached address is 23 bits wide: TTTTTTTTTLLLLLLLLLLLLBB (9 bit cache line tag, 12 bit cache line, 2 bit byte offset)
|
||||
--
|
||||
-- cache lines use 4096 x 36 bit BRAM
|
||||
-- cache tags use 4096 x 9 bit BRAM
|
||||
|
||||
library IEEE;
|
||||
use IEEE.std_logic_1164.all;
|
||||
use IEEE.numeric_std.all;
|
||||
|
||||
entity DRAM is
|
||||
generic(
|
||||
sdram_address_width : natural;
|
||||
sdram_column_bits : natural;
|
||||
sdram_startup_cycles: natural;
|
||||
cycles_per_refresh : natural
|
||||
);
|
||||
port(
|
||||
-- interface to the system
|
||||
clk : in std_logic;
|
||||
reset : in std_logic;
|
||||
cs : in std_logic;
|
||||
req_read : in std_logic;
|
||||
req_write : in std_logic;
|
||||
mem_address : in std_logic_vector(24 downto 0);
|
||||
data_in : in std_logic_vector(7 downto 0);
|
||||
data_out : out std_logic_vector(7 downto 0) := (others => '0');
|
||||
mem_wait : out std_logic;
|
||||
coldboot : out std_logic; -- this signals 1 until the SDRAM has been initialised
|
||||
|
||||
-- interface to hardware SDRAM chip
|
||||
SDRAM_CLK : out std_logic;
|
||||
SDRAM_CKE : out std_logic;
|
||||
SDRAM_CS : out std_logic;
|
||||
SDRAM_nRAS : out std_logic;
|
||||
SDRAM_nCAS : out std_logic;
|
||||
SDRAM_nWE : out std_logic;
|
||||
SDRAM_DQM : out std_logic_vector( 1 downto 0);
|
||||
SDRAM_ADDR : out std_logic_vector (12 downto 0);
|
||||
SDRAM_BA : out std_logic_vector( 1 downto 0);
|
||||
SDRAM_DQ : inout std_logic_vector (15 downto 0)
|
||||
);
|
||||
end DRAM;
|
||||
|
||||
architecture behaviour of DRAM is
|
||||
-- sdram controller interface
|
||||
signal cmd_address : std_logic_vector(sdram_address_width-2 downto 0) := (others => '0');
|
||||
signal cmd_wr : std_logic := '1';
|
||||
signal cmd_enable : std_logic;
|
||||
signal cmd_byte_enable : std_logic_vector(3 downto 0);
|
||||
signal cmd_data_in : std_logic_vector(31 downto 0);
|
||||
signal cmd_ready : std_logic;
|
||||
signal sdram_data_out : std_logic_vector(31 downto 0);
|
||||
signal sdram_data_out_ready : std_logic;
|
||||
signal seen_ready : std_logic := '0';
|
||||
signal last_address_word : std_logic_vector(20 downto 0);
|
||||
|
||||
-- internal signals
|
||||
signal current_word : std_logic_vector(31 downto 0); -- value of current cache line
|
||||
signal current_byte_valid : std_logic_vector(3 downto 0); -- validity bits for current cache line
|
||||
signal word_changed : std_logic; -- did the address bus value change?
|
||||
signal cache_hit : std_logic;
|
||||
signal address_hit : std_logic;
|
||||
signal byte_valid_hit : std_logic;
|
||||
signal write_back : std_logic;
|
||||
|
||||
-- state machine
|
||||
type controller_state is ( st_idle, -- waiting for command
|
||||
st_read, -- cache miss: issued read command to controller, waiting for data to arrive
|
||||
st_read_done, -- cache hit/completed miss: data arrived from controller, waiting for CPU to de-assert
|
||||
st_write); -- write: issued write command, waiting for CPU to de-assert
|
||||
signal current_state : controller_state;
|
||||
signal next_state : controller_state;
|
||||
|
||||
-- here's the cache memory signals
|
||||
signal cache_line_memory_write_enable : std_logic;
|
||||
signal cache_line_memory_data_in : std_logic_vector(35 downto 0); -- 35 downto 32: validity bits; 31 downto 0: four data bytes
|
||||
signal cache_line_memory_data_out : std_logic_vector(35 downto 0);
|
||||
signal cache_tag_memory_write_enable : std_logic;
|
||||
signal cache_tag_memory_data_in : std_logic_vector(8 downto 0);
|
||||
signal cache_tag_memory_data_out : std_logic_vector(8 downto 0);
|
||||
|
||||
-- break up the incoming physical address
|
||||
alias address_byte : std_logic_vector(1 downto 0) is mem_address(1 downto 0);
|
||||
alias address_line : std_logic_vector(11 downto 0) is mem_address(13 downto 2);
|
||||
alias address_tag : std_logic_vector(8 downto 0) is mem_address(22 downto 14);
|
||||
alias address_word : std_logic_vector(20 downto 0) is mem_address(22 downto 2);
|
||||
-- mem_address(23) is unused in this design
|
||||
alias cache_inhibit : std_logic is mem_address(24);
|
||||
|
||||
begin
|
||||
|
||||
-- this should be based on the generic, really
|
||||
cmd_address <= mem_address(22 downto 2); -- address_tag & address_line
|
||||
cmd_data_in <= data_in & data_in & data_in & data_in; -- write the same data four times
|
||||
cmd_wr <= req_write;
|
||||
cache_tag_memory_data_in <= address_tag;
|
||||
coldboot <= not seen_ready;
|
||||
|
||||
compute_next_state: process(req_read, req_write, current_state, cache_hit, cmd_ready, cs, sdram_data_out_ready, word_changed)
|
||||
begin
|
||||
cmd_enable <= '0';
|
||||
mem_wait <= '0';
|
||||
write_back <= '0';
|
||||
case current_state is
|
||||
when st_idle =>
|
||||
if cs = '1' and cmd_ready = '1' then
|
||||
if req_read = '1' then
|
||||
-- we can't process a read immediately if the address input just changed; delay them for one cycle.
|
||||
if word_changed = '1' then
|
||||
mem_wait <= '1';
|
||||
next_state <= st_idle;
|
||||
-- come back next cycle!
|
||||
else
|
||||
if cache_hit = '1' then
|
||||
mem_wait <= '0';
|
||||
next_state <= st_read_done;
|
||||
else
|
||||
cmd_enable <= '1';
|
||||
mem_wait <= '1';
|
||||
next_state <= st_read;
|
||||
end if;
|
||||
end if;
|
||||
elsif req_write = '1' then
|
||||
if word_changed = '1' then
|
||||
mem_wait <= '1';
|
||||
next_state <= st_idle;
|
||||
-- come back next cycle!
|
||||
else
|
||||
next_state <= st_write;
|
||||
cmd_enable <= '1';
|
||||
mem_wait <= '0'; -- no need to wait, the SDRAM controller will latch all the inputs
|
||||
write_back <= '1';
|
||||
end if;
|
||||
else
|
||||
next_state <= st_idle;
|
||||
mem_wait <= '0'; -- we know cmd_ready='1'
|
||||
end if;
|
||||
else
|
||||
next_state <= st_idle;
|
||||
mem_wait <= (not cmd_ready);
|
||||
end if;
|
||||
when st_read =>
|
||||
if cs = '1' and req_read = '1' then
|
||||
if sdram_data_out_ready = '1' then
|
||||
next_state <= st_read_done;
|
||||
else
|
||||
next_state <= st_read;
|
||||
end if;
|
||||
else
|
||||
-- this kind of implies that they gave up on us?
|
||||
next_state <= st_idle;
|
||||
end if;
|
||||
mem_wait <= (not sdram_data_out_ready) and (not cache_hit);
|
||||
when st_read_done =>
|
||||
if cs = '1' and req_read = '1' then
|
||||
next_state <= st_read_done;
|
||||
else
|
||||
next_state <= st_idle;
|
||||
end if;
|
||||
mem_wait <= (not sdram_data_out_ready) and (not cache_hit);
|
||||
when st_write =>
|
||||
if cs = '1' and req_write = '1' then
|
||||
next_state <= st_write;
|
||||
else
|
||||
next_state <= st_idle;
|
||||
end if;
|
||||
mem_wait <= (not cmd_ready); -- no need to wait once the write has been committed
|
||||
end case;
|
||||
end process;
|
||||
|
||||
word_changed_check: process(last_address_word, address_word)
|
||||
begin
|
||||
if address_word = last_address_word then
|
||||
word_changed <= '0';
|
||||
else
|
||||
word_changed <= '1';
|
||||
end if;
|
||||
end process;
|
||||
|
||||
cache_address_check: process(cache_tag_memory_data_out, cache_line_memory_data_out, address_tag)
|
||||
begin
|
||||
if cache_tag_memory_data_out = address_tag then
|
||||
address_hit <= '1';
|
||||
current_byte_valid <= cache_line_memory_data_out(35 downto 32);
|
||||
else
|
||||
address_hit <= '0';
|
||||
current_byte_valid <= "0000";
|
||||
end if;
|
||||
end process;
|
||||
|
||||
cache_byte_valid_check: process(address_byte, current_byte_valid)
|
||||
begin
|
||||
case address_byte is
|
||||
when "00" => byte_valid_hit <= current_byte_valid(0);
|
||||
when "01" => byte_valid_hit <= current_byte_valid(1);
|
||||
when "10" => byte_valid_hit <= current_byte_valid(2);
|
||||
when "11" => byte_valid_hit <= current_byte_valid(3);
|
||||
when others => byte_valid_hit <= '0';
|
||||
end case;
|
||||
end process;
|
||||
|
||||
cache_hit_check: process(byte_valid_hit, address_hit, cache_inhibit)
|
||||
begin
|
||||
if address_hit = '1' and byte_valid_hit = '1' and cache_inhibit = '0' then
|
||||
cache_hit <= '1';
|
||||
else
|
||||
cache_hit <= '0';
|
||||
end if;
|
||||
end process;
|
||||
|
||||
byte_enable_decode: process(address_byte)
|
||||
begin
|
||||
case address_byte is
|
||||
when "00" => cmd_byte_enable <= "0001";
|
||||
when "01" => cmd_byte_enable <= "0010";
|
||||
when "10" => cmd_byte_enable <= "0100";
|
||||
when "11" => cmd_byte_enable <= "1000";
|
||||
when others => cmd_byte_enable <= "1000";
|
||||
end case;
|
||||
end process;
|
||||
|
||||
data_out_demux: process(address_byte, sdram_data_out_ready, sdram_data_out, cache_line_memory_data_out, current_word)
|
||||
begin
|
||||
-- when the SDRAM is presenting data, feed it direct to the CPU.
|
||||
-- otherwise feed data from our cache memory.
|
||||
if sdram_data_out_ready = '1' then
|
||||
current_word <= sdram_data_out;
|
||||
else
|
||||
current_word <= cache_line_memory_data_out(31 downto 0);
|
||||
end if;
|
||||
|
||||
case address_byte is
|
||||
when "00" => data_out <= current_word( 7 downto 0);
|
||||
when "01" => data_out <= current_word(15 downto 8);
|
||||
when "10" => data_out <= current_word(23 downto 16);
|
||||
when "11" => data_out <= current_word(31 downto 24);
|
||||
when others => data_out <= current_word(31 downto 24);
|
||||
end case;
|
||||
end process;
|
||||
|
||||
cache_write: process(current_state, data_in, next_state, write_back, cache_line_memory_data_out, sdram_data_out, sdram_data_out_ready, address_byte, current_byte_valid)
|
||||
begin
|
||||
if (next_state = st_read) or (write_back = '1') then
|
||||
cache_tag_memory_write_enable <= '1';
|
||||
else
|
||||
cache_tag_memory_write_enable <= '0';
|
||||
end if;
|
||||
|
||||
cache_line_memory_write_enable <= '0';
|
||||
cache_line_memory_data_in <= cache_line_memory_data_out;
|
||||
|
||||
if next_state = st_read then
|
||||
cache_line_memory_data_in <= (others => '0'); -- set word and all valid flags to 1
|
||||
cache_line_memory_write_enable <= '1';
|
||||
end if;
|
||||
|
||||
-- has our read completed?
|
||||
if current_state = st_read then
|
||||
if sdram_data_out_ready = '1' then
|
||||
cache_line_memory_data_in <= "1111" & sdram_data_out;
|
||||
cache_line_memory_write_enable <= '1';
|
||||
end if;
|
||||
elsif write_back = '1' then
|
||||
case address_byte is
|
||||
when "00" =>
|
||||
cache_line_memory_data_in <= current_byte_valid(3 downto 1) & "1" &
|
||||
cache_line_memory_data_out(31 downto 8) & data_in;
|
||||
when "01" =>
|
||||
cache_line_memory_data_in <=
|
||||
current_byte_valid(3 downto 2) & "1" & current_byte_valid(0) &
|
||||
cache_line_memory_data_out(31 downto 16) & data_in & cache_line_memory_data_out(7 downto 0);
|
||||
when "10" =>
|
||||
cache_line_memory_data_in <=
|
||||
current_byte_valid(3) & "1" & current_byte_valid(1 downto 0) &
|
||||
cache_line_memory_data_out(31 downto 24) & data_in & cache_line_memory_data_out(15 downto 0);
|
||||
when "11" =>
|
||||
cache_line_memory_data_in <=
|
||||
"1" & current_byte_valid(2 downto 0) &
|
||||
data_in & cache_line_memory_data_out(23 downto 0);
|
||||
when others => -- shut up, compiler!
|
||||
end case;
|
||||
cache_line_memory_write_enable <= '1';
|
||||
end if;
|
||||
end process;
|
||||
|
||||
sdram_registers: process(clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
-- state register
|
||||
current_state <= next_state;
|
||||
-- coldboot detection
|
||||
seen_ready <= seen_ready or cmd_ready;
|
||||
-- track memory address
|
||||
last_address_word <= address_word;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
-- underlying SDRAM controller (thanks, Hamsterworks!)
|
||||
sdram_ctrl: entity work.SDRAM_Controller
|
||||
GENERIC MAP(
|
||||
sdram_address_width => sdram_address_width,
|
||||
sdram_column_bits => sdram_column_bits,
|
||||
sdram_startup_cycles=> sdram_startup_cycles,
|
||||
cycles_per_refresh => cycles_per_refresh
|
||||
)
|
||||
PORT MAP(
|
||||
clk => clk,
|
||||
reset => reset,
|
||||
|
||||
cmd_address => cmd_address,
|
||||
cmd_wr => cmd_wr,
|
||||
cmd_enable => cmd_enable,
|
||||
cmd_ready => cmd_ready,
|
||||
cmd_byte_enable => cmd_byte_enable,
|
||||
cmd_data_in => cmd_data_in,
|
||||
|
||||
data_out => sdram_data_out,
|
||||
data_out_ready => sdram_data_out_ready,
|
||||
|
||||
SDRAM_CLK => SDRAM_CLK,
|
||||
SDRAM_CKE => SDRAM_CKE,
|
||||
SDRAM_CS => SDRAM_CS,
|
||||
SDRAM_RAS => SDRAM_nRAS,
|
||||
SDRAM_CAS => SDRAM_nCAS,
|
||||
SDRAM_WE => SDRAM_nWE,
|
||||
SDRAM_DQM => SDRAM_DQM,
|
||||
SDRAM_BA => SDRAM_BA,
|
||||
SDRAM_ADDR => SDRAM_ADDR,
|
||||
SDRAM_DATA => SDRAM_DQ
|
||||
);
|
||||
|
||||
-- block RAM used to store cache line data and byte validity (packs nicely into 36 bits)
|
||||
cacheline_memory_sram: entity work.RAM4K36
|
||||
port map (
|
||||
clk => clk,
|
||||
reset => reset,
|
||||
write => cache_line_memory_write_enable,
|
||||
address => address_line,
|
||||
data_in => cache_line_memory_data_in,
|
||||
data_out => cache_line_memory_data_out
|
||||
|
||||
);
|
||||
|
||||
-- block RAM used to store cache line tag memory (packs nicely into 9 bits)
|
||||
cachetag_memory_sram: entity work.RAM4K9
|
||||
port map (
|
||||
clk => clk,
|
||||
reset => reset,
|
||||
write => cache_tag_memory_write_enable,
|
||||
address => address_line,
|
||||
data_in => cache_tag_memory_data_in,
|
||||
data_out => cache_tag_memory_data_out
|
||||
);
|
||||
end;
|
||||
319
vhdl/MMU.vhd
Normal file
319
vhdl/MMU.vhd
Normal file
@ -0,0 +1,319 @@
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
|
||||
--| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
|
||||
--| / __|/ _ \ / __|_ / _ \| | | | | |--
|
||||
--| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
|
||||
--| |___/\___/ \___/___\___/ \___/ | |--
|
||||
--| | http://sowerbutts.com/ |--
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| 4K paged Memory Management Unit: Translates 16-bit virtual addresses |--
|
||||
--| from the CPU into 26-bit physical addresses to allow more memory to be |--
|
||||
--| addressed. Also has a hack to allow unmapped physical memory to be |--
|
||||
--| accessed through an IO port which synthesises memory operations. |--
|
||||
--+-------------------------------------------------------------------------+--
|
||||
--
|
||||
-- The MMU takes a 16-bit virtual address from the CPU and divides it into a
|
||||
-- 4-bit frame number and a 12-bit offset. The frame number is used as an index
|
||||
-- into an array of sixteen registers which contain the hardware page numbers
|
||||
-- (the translation table). The physical address is then formed from the
|
||||
-- hardware page number concatenated with the 12-bit offset. My hardware page
|
||||
-- numbers are 14-bits long because I wanted a 64MB physical address space but
|
||||
-- you could use any length you wished.
|
||||
--
|
||||
-- So if the virtual address 0xABCD is accessed, we'd divide that into frame
|
||||
-- number 0xA (decimal 10), offset 0xBCD. If the 10th MMU translation register
|
||||
-- contains 0x1234 then the translated physical address would be 0x1234BCD.
|
||||
--
|
||||
-- My MMU is programmed using 8 I/O ports in the range 0xF8 through 0xFF. The
|
||||
-- chip select line is asserted for any access in that range and the MMU
|
||||
-- decodes the low three bits to determine which register is being accessed.
|
||||
--
|
||||
-- The port at 0xF8 is effectively a mux which selects the function of ports
|
||||
-- 0xFB through 0xFF.
|
||||
--
|
||||
-- Writing 0x00 through 0x0F to the function register at 0xF8 allows you to
|
||||
-- read/write one of the 16 MMU translation registers. With these selected;
|
||||
-- - 0xFC contains the high byte of the physical address
|
||||
-- - 0xFD contains the low byte of the physical address
|
||||
-- - 0xFB contains permission bits (read/write/execute, currently ignored)
|
||||
--
|
||||
-- So updating a mapping generally requires just three I/O writes: One to 0xF8
|
||||
-- to select which frame to modify, and one each to 0xFC and 0xFD to write out
|
||||
-- the new translation. The permission bits are programmable but currently
|
||||
-- ignored (I had planned to add some level of memory protection to UZI one day)
|
||||
--
|
||||
-- The "17th page" is a bit of a hack bolted on. The lazy programmer in me finds
|
||||
-- it much more convenient to sometimes access memory without remapping a frame;
|
||||
-- in particular you don't have to select which frame to remap such as to avoid
|
||||
-- remapping the memory pointed to by PC, SP or your source/target pointer.
|
||||
--
|
||||
-- Writing 0xFF to port 0xF8 selects the "17th page pointer". This is a 26-bit
|
||||
-- register but again could be wider/narrower as required. With this selected
|
||||
-- ports 0xFC, 0xFD, 0xFE, 0xFF are the register contents (with the high byte in
|
||||
-- 0xFC, low byte in 0xFF).
|
||||
--
|
||||
-- When the CPU reads or writes the I/O port 0xFA the MMU translates the I/O
|
||||
-- operation into a memory operation. The physical memory address accessed is
|
||||
-- the address contained in the 17th page pointer register. After the memory
|
||||
-- operation completes the 17th page pointer register is incremented so that
|
||||
-- repeated accesses to 0xFA walk forward through memory.
|
||||
--
|
||||
--
|
||||
-- MMU registers:
|
||||
--
|
||||
-- base = 0xF8 in standard socz80 system
|
||||
--
|
||||
-- base+0 mux frame select (write 00..0F to select frame, FF to select "17th page" pointer)
|
||||
-- base+1 (unused)
|
||||
-- base+2 17th page (read/write will address pointed memory and post-increment the pointer)
|
||||
-- base+3 page permissions
|
||||
-- base+4 page address (high byte) / ptr address (high byte)
|
||||
-- base+5 page address (low byte) / ptr address
|
||||
-- base+6 ptr address
|
||||
-- base+7 ptr address (low byte)
|
||||
--
|
||||
-- Basic operation of the MMU (older documentation, concise but still correct)
|
||||
-- For memory requests:
|
||||
-- The top 4 bits of CPU logical address are replaced with 14 bits taken
|
||||
-- from the MMU translation table entry indexed by those top four bits.
|
||||
-- For IO requests:
|
||||
-- Reads or writes to I/O port at (base+2) are converted into memory
|
||||
-- requests to the address pointed to by "the 17th page", a pointer which
|
||||
-- can be accessed by writing 0xFF to the frame select register (base+0)
|
||||
-- and then programming a 26-bit address into I/O ports FC FD FE FF. Each
|
||||
-- I/O request to port F/A results in the pointer being post-incremented
|
||||
-- by 1, which means that INIR our OUTIR instructions can read/write blocks
|
||||
-- of memory outside of the CPU logical address space through this port.
|
||||
--
|
||||
-- Note that the MMU has to insert a forced CPU wait state to give the
|
||||
-- addressed memory device time to read the new address off the bus.
|
||||
--
|
||||
|
||||
library IEEE;
|
||||
use IEEE.std_logic_1164.all;
|
||||
use IEEE.numeric_std.all;
|
||||
|
||||
entity MMU is
|
||||
port(
|
||||
clk : in std_logic;
|
||||
reset : in std_logic;
|
||||
address_in : in std_logic_vector(15 downto 0);
|
||||
address_out : out std_logic_vector(25 downto 0);
|
||||
cpu_data_in : in std_logic_vector(7 downto 0);
|
||||
cpu_data_out : out std_logic_vector(7 downto 0);
|
||||
cpu_wait : out std_logic;
|
||||
req_mem_in : in std_logic;
|
||||
req_mem_out : out std_logic;
|
||||
req_io_in : in std_logic;
|
||||
req_io_out : out std_logic;
|
||||
io_cs : in std_logic;
|
||||
req_read : in std_logic;
|
||||
req_write : in std_logic;
|
||||
access_violated : out std_logic
|
||||
);
|
||||
end MMU;
|
||||
|
||||
architecture behaviour of MMU is
|
||||
-- each MMU entry looks like this
|
||||
type mmu_entry_type is
|
||||
record
|
||||
frame: std_logic_vector(13 downto 0);
|
||||
can_read: std_logic;
|
||||
can_write: std_logic;
|
||||
end record;
|
||||
|
||||
-- the whole MMU state looks like this
|
||||
type mmu_entry_array is array(natural range <>) of mmu_entry_type;
|
||||
|
||||
-- and here's our instance
|
||||
signal mmu_entry : mmu_entry_array(0 to 15);
|
||||
|
||||
-- 17th page pointer (frame FF)
|
||||
signal mmu_frame_ff_pointer : std_logic_vector(25 downto 0);
|
||||
|
||||
-- IO interface
|
||||
signal cpu_entry_select : std_logic_vector(7 downto 0);
|
||||
|
||||
-- break up the incoming virtual address
|
||||
alias frame_number : std_logic_vector( 3 downto 0) is address_in(15 downto 12);
|
||||
alias page_offset : std_logic_vector(11 downto 0) is address_in(11 downto 0);
|
||||
|
||||
signal map_io_to_ff_ptr : std_logic;
|
||||
signal was_map_io_to_ff_ptr : std_logic := '0';
|
||||
begin
|
||||
|
||||
map_io_to_mem_proc: process(address_in, req_mem_in, req_io_in)
|
||||
begin
|
||||
if req_mem_in = '0' and req_io_in = '1' and address_in(7 downto 0) = "11111010" then
|
||||
map_io_to_ff_ptr <= '1';
|
||||
else
|
||||
map_io_to_ff_ptr <= '0';
|
||||
end if;
|
||||
end process;
|
||||
|
||||
with map_io_to_ff_ptr select
|
||||
address_out <=
|
||||
mmu_entry(to_integer(unsigned(frame_number))).frame & page_offset when '0',
|
||||
mmu_frame_ff_pointer when '1';
|
||||
|
||||
with map_io_to_ff_ptr select
|
||||
req_mem_out <=
|
||||
req_mem_in when '0',
|
||||
'1' when '1';
|
||||
|
||||
with map_io_to_ff_ptr select
|
||||
req_io_out <=
|
||||
req_io_in when '0',
|
||||
'0' when '1';
|
||||
|
||||
with map_io_to_ff_ptr select
|
||||
access_violated <=
|
||||
req_mem_in and ((req_read and not mmu_entry(to_integer(unsigned(frame_number))).can_read) or
|
||||
(req_write and not mmu_entry(to_integer(unsigned(frame_number))).can_write)) when '0',
|
||||
'0' when '1';
|
||||
|
||||
-- force CPU to wait one cycle when we map IO to memory access; this
|
||||
-- is in order to give our synchronous memories a cycle to read the
|
||||
-- address, look up the data in synchronous memory, and provide a result.
|
||||
cpu_wait <= map_io_to_ff_ptr and (not was_map_io_to_ff_ptr);
|
||||
|
||||
data_out: process(address_in, cpu_entry_select, mmu_entry, mmu_frame_ff_pointer)
|
||||
begin
|
||||
if cpu_entry_select = "11111111" then
|
||||
-- pointer (FF)
|
||||
case address_in(2 downto 0) is
|
||||
when "000" =>
|
||||
cpu_data_out <= cpu_entry_select;
|
||||
when "011" =>
|
||||
cpu_data_out <= "00000011";
|
||||
when "100" =>
|
||||
cpu_data_out <= "000000" & mmu_frame_ff_pointer(25 downto 24);
|
||||
when "101" =>
|
||||
cpu_data_out <= mmu_frame_ff_pointer(23 downto 16);
|
||||
when "110" =>
|
||||
cpu_data_out <= mmu_frame_ff_pointer(15 downto 8);
|
||||
when "111" =>
|
||||
cpu_data_out <= mmu_frame_ff_pointer(7 downto 0);
|
||||
when others =>
|
||||
cpu_data_out <= "00000000";
|
||||
end case;
|
||||
else
|
||||
-- 16 frames (00 .. 0F)
|
||||
case address_in(2 downto 0) is
|
||||
when "000" =>
|
||||
cpu_data_out <= cpu_entry_select;
|
||||
when "011" =>
|
||||
cpu_data_out <= "000000" & mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).can_write & mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).can_read;
|
||||
when "100" =>
|
||||
cpu_data_out <= "00" & mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(13 downto 8);
|
||||
when "101" =>
|
||||
cpu_data_out <= mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(7 downto 0);
|
||||
when others =>
|
||||
cpu_data_out <= "00000000";
|
||||
end case;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
mmu_registers: process(clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
if reset = '1' then
|
||||
mmu_entry( 0).frame <= "10000000000000"; -- first page of SRAM (monitor ROM)
|
||||
mmu_entry( 1).frame <= "00000000000001"; -- DRAM page 1
|
||||
mmu_entry( 2).frame <= "00000000000010"; -- DRAM page 2
|
||||
mmu_entry( 3).frame <= "00000000000011"; -- DRAM page 3
|
||||
mmu_entry( 4).frame <= "00000000000100"; -- DRAM page 4
|
||||
mmu_entry( 5).frame <= "00000000000101"; -- DRAM page 5
|
||||
mmu_entry( 6).frame <= "00000000000110"; -- DRAM page 6
|
||||
mmu_entry( 7).frame <= "00000000000111"; -- DRAM page 7
|
||||
mmu_entry( 8).frame <= "00000000001000"; -- DRAM page 8
|
||||
mmu_entry( 9).frame <= "00000000001001"; -- DRAM page 9
|
||||
mmu_entry(10).frame <= "00000000001010"; -- DRAM page 10
|
||||
mmu_entry(11).frame <= "00000000001011"; -- DRAM page 11
|
||||
mmu_entry(12).frame <= "00000000001100"; -- DRAM page 12
|
||||
mmu_entry(13).frame <= "00000000001101"; -- DRAM page 13
|
||||
mmu_entry(14).frame <= "00000000001110"; -- DRAM page 14
|
||||
mmu_entry(15).frame <= "10000000000001"; -- second page of SRAM
|
||||
mmu_entry( 0).can_read <= '1';
|
||||
mmu_entry( 1).can_read <= '1';
|
||||
mmu_entry( 2).can_read <= '1';
|
||||
mmu_entry( 3).can_read <= '1';
|
||||
mmu_entry( 4).can_read <= '1';
|
||||
mmu_entry( 5).can_read <= '1';
|
||||
mmu_entry( 6).can_read <= '1';
|
||||
mmu_entry( 7).can_read <= '1';
|
||||
mmu_entry( 8).can_read <= '1';
|
||||
mmu_entry( 9).can_read <= '1';
|
||||
mmu_entry(10).can_read <= '1';
|
||||
mmu_entry(11).can_read <= '1';
|
||||
mmu_entry(12).can_read <= '1';
|
||||
mmu_entry(13).can_read <= '1';
|
||||
mmu_entry(14).can_read <= '1';
|
||||
mmu_entry(15).can_read <= '1';
|
||||
mmu_entry( 0).can_write <= '0';
|
||||
mmu_entry( 1).can_write <= '1';
|
||||
mmu_entry( 2).can_write <= '1';
|
||||
mmu_entry( 3).can_write <= '1';
|
||||
mmu_entry( 4).can_write <= '1';
|
||||
mmu_entry( 5).can_write <= '1';
|
||||
mmu_entry( 6).can_write <= '1';
|
||||
mmu_entry( 7).can_write <= '1';
|
||||
mmu_entry( 8).can_write <= '1';
|
||||
mmu_entry( 9).can_write <= '1';
|
||||
mmu_entry(10).can_write <= '1';
|
||||
mmu_entry(11).can_write <= '1';
|
||||
mmu_entry(12).can_write <= '1';
|
||||
mmu_entry(13).can_write <= '1';
|
||||
mmu_entry(14).can_write <= '1';
|
||||
mmu_entry(15).can_write <= '1';
|
||||
mmu_frame_ff_pointer <= "10000000000000000000000000"; -- map first byte of ROM to pointer on reset
|
||||
was_map_io_to_ff_ptr <= '0';
|
||||
else
|
||||
was_map_io_to_ff_ptr <= map_io_to_ff_ptr;
|
||||
|
||||
if io_cs = '1' and req_write = '1' then
|
||||
case address_in(2 downto 0) is
|
||||
when "000" =>
|
||||
cpu_entry_select <= cpu_data_in;
|
||||
when "011" =>
|
||||
if cpu_entry_select(7 downto 4) = "0000" then
|
||||
mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).can_read <= cpu_data_in(0);
|
||||
mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).can_write <= cpu_data_in(1);
|
||||
end if;
|
||||
when "100" =>
|
||||
if cpu_entry_select(7 downto 4) = "0000" then
|
||||
mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(13 downto 0) <=
|
||||
cpu_data_in(5 downto 0) & mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(7 downto 0);
|
||||
elsif cpu_entry_select = "11111111" then
|
||||
mmu_frame_ff_pointer <=
|
||||
cpu_data_in(1 downto 0) & mmu_frame_ff_pointer(23 downto 0);
|
||||
end if;
|
||||
when "101" =>
|
||||
if cpu_entry_select(7 downto 4) = "0000" then
|
||||
mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(13 downto 0) <=
|
||||
mmu_entry(to_integer(unsigned(cpu_entry_select(3 downto 0)))).frame(13 downto 8) & cpu_data_in(7 downto 0);
|
||||
elsif cpu_entry_select = "11111111" then
|
||||
mmu_frame_ff_pointer <=
|
||||
mmu_frame_ff_pointer(25 downto 24) & cpu_data_in(7 downto 0) & mmu_frame_ff_pointer(15 downto 0);
|
||||
end if;
|
||||
when "110" =>
|
||||
if cpu_entry_select = "11111111" then
|
||||
mmu_frame_ff_pointer <=
|
||||
mmu_frame_ff_pointer(25 downto 16) & cpu_data_in(7 downto 0) & mmu_frame_ff_pointer(7 downto 0);
|
||||
end if;
|
||||
when "111" =>
|
||||
if cpu_entry_select = "11111111" then
|
||||
mmu_frame_ff_pointer <=
|
||||
mmu_frame_ff_pointer(25 downto 8) & cpu_data_in(7 downto 0);
|
||||
end if;
|
||||
when others =>
|
||||
-- nothing
|
||||
end case;
|
||||
elsif map_io_to_ff_ptr = '0' and was_map_io_to_ff_ptr = '1' then
|
||||
-- post-increment our pointer (this is what makes "the 17th page" efficient!)
|
||||
mmu_frame_ff_pointer <= std_logic_vector(unsigned(mmu_frame_ff_pointer) + 1);
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
end;
|
||||
56
vhdl/MonZ80_template.vhd
Normal file
56
vhdl/MonZ80_template.vhd
Normal file
@ -0,0 +1,56 @@
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
|
||||
--| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
|
||||
--| / __|/ _ \ / __|_ / _ \| | | | | |--
|
||||
--| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
|
||||
--| |___/\___/ \___/___\___/ \___/ | |--
|
||||
--| | http://sowerbutts.com/ |--
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| An inferrable 4KB ROM to contain the monitor program |--
|
||||
--+-------------------------------------------------------------------------+--
|
||||
--
|
||||
-- MonZ80_template.vhd contains the template VHDL for the ROM but no actual
|
||||
-- data. The "ROMHERE" string is replaced by byte data by the "make_vhdl_rom"
|
||||
-- tool in software/tools which is invoked to generate "MonZ80.vhd" after
|
||||
-- the monitor program has been assembled.
|
||||
|
||||
library ieee;
|
||||
use ieee.std_logic_1164.all;
|
||||
use ieee.numeric_std.all;
|
||||
|
||||
entity MonZ80 is
|
||||
port(
|
||||
clk : in std_logic;
|
||||
a : in std_logic_vector(11 downto 0);
|
||||
d : out std_logic_vector(7 downto 0)
|
||||
);
|
||||
end MonZ80;
|
||||
|
||||
architecture arch of MonZ80 is
|
||||
constant byte_rom_WIDTH: integer := 8;
|
||||
type byte_rom_type is array (0 to 4095) of std_logic_vector(byte_rom_WIDTH-1 downto 0);
|
||||
signal address_latch : std_logic_vector(11 downto 0) := (others => '0');
|
||||
|
||||
-- actually memory cells
|
||||
signal byte_rom : byte_rom_type := (
|
||||
-- ROM contents follows
|
||||
|
||||
|
||||
%ROMHERE%
|
||||
|
||||
|
||||
);
|
||||
|
||||
begin
|
||||
|
||||
ram_process: process(clk, byte_rom)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
-- latch the address, in order to infer a synchronous memory
|
||||
address_latch <= a;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
d <= byte_rom(to_integer(unsigned(address_latch)));
|
||||
|
||||
end arch;
|
||||
506
vhdl/SDRAM_Controller.vhd
Normal file
506
vhdl/SDRAM_Controller.vhd
Normal file
@ -0,0 +1,506 @@
|
||||
----------------------------------------------------------------------------------
|
||||
-- Engineer: Mike Field <hamster@snap.net.nz>
|
||||
--
|
||||
-- Create Date: 14:09:12 09/15/2013
|
||||
-- Module Name: SDRAM_Controller - Behavioral
|
||||
-- Description: Simple SDRAM controller for a Micron 48LC16M16A2-7E
|
||||
-- or Micron 48LC4M16A2-7E @ 100MHz
|
||||
-- Revision:
|
||||
-- Revision 0.1 - Initial version
|
||||
-- Revision 0.2 - Removed second clock signal that isn't needed.
|
||||
-- Revision 0.3 - Added back-to-back reads and writes.
|
||||
-- Revision 0.4 - Allow refeshes to be delayed till next PRECHARGE is issued,
|
||||
-- Unless they get really, really delayed. If a delay occurs multiple
|
||||
-- refreshes might get pushed out, but it will have avioded about
|
||||
-- 50% of the refresh overhead
|
||||
-- Revision 0.5 - Add more paramaters to the design, allowing it to work for both the
|
||||
-- Papilio Pro and Logi-Pi
|
||||
--
|
||||
-- Worst case performance (single accesses to different rows or banks) is:
|
||||
-- Writes 16 cycles = 6,250,000 writes/sec = 25.0MB/s (excluding refresh overhead)
|
||||
-- Reads 17 cycles = 5,882,352 reads/sec = 23.5MB/s (excluding refresh overhead)
|
||||
--
|
||||
-- For 1:1 mixed reads and writes into the same row it is around 88MB/s
|
||||
-- For reads or wries to the same it is can be as high as 184MB/s
|
||||
----------------------------------------------------------------------------------
|
||||
library IEEE;
|
||||
use IEEE.STD_LOGIC_1164.ALL;
|
||||
library UNISIM;
|
||||
use UNISIM.VComponents.all;
|
||||
use IEEE.NUMERIC_STD.ALL;
|
||||
|
||||
|
||||
entity SDRAM_Controller is
|
||||
generic (
|
||||
sdram_address_width : natural;
|
||||
sdram_column_bits : natural;
|
||||
sdram_startup_cycles: natural;
|
||||
cycles_per_refresh : natural
|
||||
);
|
||||
Port ( clk : in STD_LOGIC;
|
||||
reset : in STD_LOGIC;
|
||||
|
||||
-- Interface to issue reads or write data
|
||||
cmd_ready : out STD_LOGIC; -- '1' when a new command will be acted on
|
||||
cmd_enable : in STD_LOGIC; -- Set to '1' to issue new command (only acted on when cmd_read = '1')
|
||||
cmd_wr : in STD_LOGIC; -- Is this a write?
|
||||
cmd_address : in STD_LOGIC_VECTOR(sdram_address_width-2 downto 0); -- address to read/write
|
||||
cmd_byte_enable : in STD_LOGIC_VECTOR(3 downto 0); -- byte masks for the write command
|
||||
cmd_data_in : in STD_LOGIC_VECTOR(31 downto 0); -- data for the write command
|
||||
|
||||
data_out : out STD_LOGIC_VECTOR(31 downto 0); -- word read from SDRAM
|
||||
data_out_ready : out STD_LOGIC; -- is new data ready?
|
||||
|
||||
-- SDRAM signals
|
||||
SDRAM_CLK : out STD_LOGIC;
|
||||
SDRAM_CKE : out STD_LOGIC;
|
||||
SDRAM_CS : out STD_LOGIC;
|
||||
SDRAM_RAS : out STD_LOGIC;
|
||||
SDRAM_CAS : out STD_LOGIC;
|
||||
SDRAM_WE : out STD_LOGIC;
|
||||
SDRAM_DQM : out STD_LOGIC_VECTOR( 1 downto 0);
|
||||
SDRAM_ADDR : out STD_LOGIC_VECTOR(12 downto 0);
|
||||
SDRAM_BA : out STD_LOGIC_VECTOR( 1 downto 0);
|
||||
SDRAM_DATA : inout STD_LOGIC_VECTOR(15 downto 0));
|
||||
end SDRAM_Controller;
|
||||
|
||||
architecture Behavioral of SDRAM_Controller is
|
||||
-- From page 37 of MT48LC16M16A2 datasheet
|
||||
-- Name (Function) CS# RAS# CAS# WE# DQM Addr Data
|
||||
-- COMMAND INHIBIT (NOP) H X X X X X X
|
||||
-- NO OPERATION (NOP) L H H H X X X
|
||||
-- ACTIVE L L H H X Bank/row X
|
||||
-- READ L H L H L/H Bank/col X
|
||||
-- WRITE L H L L L/H Bank/col Valid
|
||||
-- BURST TERMINATE L H H L X X Active
|
||||
-- PRECHARGE L L H L X Code X
|
||||
-- AUTO REFRESH L L L H X X X
|
||||
-- LOAD MODE REGISTER L L L L X Op-code X
|
||||
-- Write enable X X X X L X Active
|
||||
-- Write inhibit X X X X H X High-Z
|
||||
|
||||
-- Here are the commands mapped to constants
|
||||
constant CMD_UNSELECTED : std_logic_vector(3 downto 0) := "1000";
|
||||
constant CMD_NOP : std_logic_vector(3 downto 0) := "0111";
|
||||
constant CMD_ACTIVE : std_logic_vector(3 downto 0) := "0011";
|
||||
constant CMD_READ : std_logic_vector(3 downto 0) := "0101";
|
||||
constant CMD_WRITE : std_logic_vector(3 downto 0) := "0100";
|
||||
constant CMD_TERMINATE : std_logic_vector(3 downto 0) := "0110";
|
||||
constant CMD_PRECHARGE : std_logic_vector(3 downto 0) := "0010";
|
||||
constant CMD_REFRESH : std_logic_vector(3 downto 0) := "0001";
|
||||
constant CMD_LOAD_MODE_REG : std_logic_vector(3 downto 0) := "0000";
|
||||
|
||||
constant MODE_REG : std_logic_vector(12 downto 0) :=
|
||||
-- Reserved, wr bust, OpMode, CAS Latency (2), Burst Type, Burst Length (2)
|
||||
"000" & "0" & "00" & "010" & "0" & "001";
|
||||
|
||||
signal iob_command : std_logic_vector( 3 downto 0) := CMD_NOP;
|
||||
signal iob_address : std_logic_vector(12 downto 0) := (others => '0');
|
||||
signal iob_data : std_logic_vector(15 downto 0) := (others => '0');
|
||||
signal iob_dqm : std_logic_vector( 1 downto 0) := (others => '0');
|
||||
signal iob_cke : std_logic := '0';
|
||||
signal iob_bank : std_logic_vector( 1 downto 0) := (others => '0');
|
||||
|
||||
attribute IOB: string;
|
||||
attribute IOB of iob_command: signal is "true";
|
||||
attribute IOB of iob_address: signal is "true";
|
||||
attribute IOB of iob_dqm : signal is "true";
|
||||
attribute IOB of iob_cke : signal is "true";
|
||||
attribute IOB of iob_bank : signal is "true";
|
||||
attribute IOB of iob_data : signal is "true";
|
||||
|
||||
signal iob_data_next : std_logic_vector(15 downto 0) := (others => '0');
|
||||
signal captured_data : std_logic_vector(15 downto 0) := (others => '0');
|
||||
signal captured_data_last : std_logic_vector(15 downto 0) := (others => '0');
|
||||
signal sdram_din : std_logic_vector(15 downto 0);
|
||||
attribute IOB of captured_data : signal is "true";
|
||||
|
||||
type fsm_state is (s_startup,
|
||||
s_idle_in_6, s_idle_in_5, s_idle_in_4, s_idle_in_3, s_idle_in_2, s_idle_in_1,
|
||||
s_idle,
|
||||
s_open_in_2, s_open_in_1,
|
||||
s_write_1, s_write_2, s_write_3,
|
||||
s_read_1, s_read_2, s_read_3, s_read_4,
|
||||
s_precharge
|
||||
);
|
||||
|
||||
signal state : fsm_state := s_startup;
|
||||
attribute FSM_ENCODING : string;
|
||||
attribute FSM_ENCODING of state : signal is "ONE-HOT";
|
||||
|
||||
-- dual purpose counter, it counts up during the startup phase, then is used to trigger refreshes.
|
||||
constant startup_refresh_max : unsigned(13 downto 0) := (others => '1');
|
||||
signal startup_refresh_count : unsigned(13 downto 0) := startup_refresh_max-to_unsigned(sdram_startup_cycles,14);
|
||||
|
||||
-- logic to decide when to refresh
|
||||
signal pending_refresh : std_logic := '0';
|
||||
signal forcing_refresh : std_logic := '0';
|
||||
|
||||
-- The incoming address is split into these three values
|
||||
signal addr_row : std_logic_vector(12 downto 0) := (others => '0');
|
||||
signal addr_col : std_logic_vector(12 downto 0) := (others => '0');
|
||||
signal addr_bank : std_logic_vector( 1 downto 0) := (others => '0');
|
||||
|
||||
signal dqm_sr : std_logic_vector( 3 downto 0) := (others => '1'); -- an extra two bits in case CAS=3
|
||||
|
||||
-- signals to hold the requested transaction before it is completed
|
||||
signal save_wr : std_logic := '0';
|
||||
signal save_row : std_logic_vector(12 downto 0);
|
||||
signal save_bank : std_logic_vector( 1 downto 0);
|
||||
signal save_col : std_logic_vector(12 downto 0);
|
||||
signal save_data_in : std_logic_vector(31 downto 0);
|
||||
signal save_byte_enable : std_logic_vector( 3 downto 0);
|
||||
|
||||
-- control when new transactions are accepted
|
||||
signal ready_for_new : std_logic := '0';
|
||||
signal got_transaction : std_logic := '0';
|
||||
|
||||
signal can_back_to_back : std_logic := '0';
|
||||
|
||||
-- signal to control the Hi-Z state of the DQ bus
|
||||
signal iob_dq_hiz : std_logic := '1';
|
||||
|
||||
-- signals for when to read the data off of the bus
|
||||
signal data_ready_delay : std_logic_vector( 4 downto 0);
|
||||
|
||||
-- bit indexes used when splitting the address into row/colum/bank.
|
||||
constant start_of_col : natural := 0;
|
||||
constant end_of_col : natural := sdram_column_bits-2;
|
||||
constant start_of_bank : natural := sdram_column_bits-1;
|
||||
constant end_of_bank : natural := sdram_column_bits;
|
||||
constant start_of_row : natural := sdram_column_bits+1;
|
||||
constant end_of_row : natural := sdram_address_width-2;
|
||||
constant prefresh_cmd : natural := 10;
|
||||
begin
|
||||
-- Indicate the need to refresh when the counter is 2048,
|
||||
-- Force a refresh when the counter is 4096 - (if a refresh is forced,
|
||||
-- multiple refresshes will be forced until the counter is below 2048
|
||||
pending_refresh <= startup_refresh_count(11);
|
||||
forcing_refresh <= startup_refresh_count(12);
|
||||
|
||||
-- tell the outside world when we can accept a new transaction;
|
||||
cmd_ready <= ready_for_new;
|
||||
----------------------------------------------------------------------------
|
||||
-- Seperate the address into row / bank / address
|
||||
----------------------------------------------------------------------------
|
||||
addr_row(end_of_row-start_of_row downto 0) <= cmd_address(end_of_row downto start_of_row); -- 12:0 <= 22:10
|
||||
addr_bank <= cmd_address(end_of_bank downto start_of_bank); -- 1:0 <= 9:8
|
||||
addr_col(sdram_column_bits-1 downto 0) <= cmd_address(end_of_col downto start_of_col) & '0'; -- 8:0 <= 7:0 & '0'
|
||||
--addr_row(12 downto 0) <= cmd_address(22 downto 10); -- 12:0 <= 22:10
|
||||
--addr_bank <= cmd_address( 9 downto 8); -- 1:0 <= 9:8
|
||||
--addr_col(8 downto 0) <= cmd_address( 7 downto 0) & '0'; -- 8:0 <= 7:0 & '0'
|
||||
|
||||
-----------------------------------------------------------
|
||||
-- Forward the SDRAM clock to the SDRAM chip - 180 degress
|
||||
-- out of phase with the control signals (ensuring setup and holdup
|
||||
-----------------------------------------------------------
|
||||
sdram_clk_forward : ODDR2
|
||||
generic map(DDR_ALIGNMENT => "NONE", INIT => '0', SRTYPE => "SYNC")
|
||||
port map (Q => sdram_clk, C0 => clk, C1 => not clk, CE => '1', R => '0', S => '0', D0 => '0', D1 => '1');
|
||||
|
||||
-----------------------------------------------
|
||||
--!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
--!! Ensure that all outputs are registered. !!
|
||||
--!! Check the pinout report to be sure !!
|
||||
--!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
-----------------------------------------------
|
||||
sdram_cke <= iob_cke;
|
||||
sdram_CS <= iob_command(3);
|
||||
sdram_RAS <= iob_command(2);
|
||||
sdram_CAS <= iob_command(1);
|
||||
sdram_WE <= iob_command(0);
|
||||
sdram_dqm <= iob_dqm;
|
||||
sdram_ba <= iob_bank;
|
||||
sdram_addr <= iob_address;
|
||||
|
||||
---------------------------------------------------------------
|
||||
-- Explicitly set up the tristate I/O buffers on the DQ signals
|
||||
---------------------------------------------------------------
|
||||
iob_dq_g: for i in 0 to 15 generate
|
||||
begin
|
||||
iob_dq_iob: IOBUF
|
||||
generic map (DRIVE => 12, IOSTANDARD => "LVTTL", SLEW => "FAST")
|
||||
port map ( O => sdram_din(i), IO => sdram_data(i), I => iob_data(i), T => iob_dq_hiz);
|
||||
end generate;
|
||||
|
||||
capture_proc: process(clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
captured_data <= sdram_din;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
main_proc: process(clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
captured_data_last <= captured_data;
|
||||
|
||||
------------------------------------------------
|
||||
-- Default state is to do nothing
|
||||
------------------------------------------------
|
||||
iob_command <= CMD_NOP;
|
||||
iob_address <= (others => '0');
|
||||
iob_bank <= (others => '0');
|
||||
|
||||
------------------------------------------------
|
||||
-- countdown for initialisation & refresh
|
||||
------------------------------------------------
|
||||
startup_refresh_count <= startup_refresh_count+1;
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- It we are ready for a new tranasction and one is being presented
|
||||
-- then accept it. Also remember what we are reading or writing,
|
||||
-- and if it can be back-to-backed with the last transaction
|
||||
-------------------------------------------------------------------
|
||||
if ready_for_new = '1' and cmd_enable = '1' then
|
||||
if save_bank = addr_bank and save_row = addr_row then
|
||||
can_back_to_back <= '1';
|
||||
else
|
||||
can_back_to_back <= '0';
|
||||
end if;
|
||||
save_row <= addr_row;
|
||||
save_bank <= addr_bank;
|
||||
save_col <= addr_col;
|
||||
save_wr <= cmd_wr;
|
||||
save_data_in <= cmd_data_in;
|
||||
save_byte_enable <= cmd_byte_enable;
|
||||
got_transaction <= '1';
|
||||
ready_for_new <= '0';
|
||||
end if;
|
||||
|
||||
------------------------------------------------
|
||||
-- Handle the data coming back from the
|
||||
-- SDRAM for the Read transaction
|
||||
------------------------------------------------
|
||||
data_out_ready <= '0';
|
||||
if data_ready_delay(0) = '1' then
|
||||
data_out <= captured_data & captured_data_last;
|
||||
data_out_ready <= '1';
|
||||
end if;
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
-- update shift registers used to choose when to present data to/from memory
|
||||
----------------------------------------------------------------------------
|
||||
data_ready_delay <= '0' & data_ready_delay(data_ready_delay'high downto 1);
|
||||
iob_dqm <= dqm_sr(1 downto 0);
|
||||
dqm_sr <= "11" & dqm_sr(dqm_sr'high downto 2);
|
||||
|
||||
case state is
|
||||
when s_startup =>
|
||||
------------------------------------------------------------------------
|
||||
-- This is the initial startup state, where we wait for at least 100us
|
||||
-- before starting the start sequence
|
||||
--
|
||||
-- The initialisation is sequence is
|
||||
-- * de-assert SDRAM_CKE
|
||||
-- * 100us wait,
|
||||
-- * assert SDRAM_CKE
|
||||
-- * wait at least one cycle,
|
||||
-- * PRECHARGE
|
||||
-- * wait 2 cycles
|
||||
-- * REFRESH,
|
||||
-- * tREF wait
|
||||
-- * REFRESH,
|
||||
-- * tREF wait
|
||||
-- * LOAD_MODE_REG
|
||||
-- * 2 cycles wait
|
||||
------------------------------------------------------------------------
|
||||
iob_CKE <= '1';
|
||||
|
||||
-- All the commands during the startup are NOPS, except these
|
||||
if startup_refresh_count = startup_refresh_max-31 then
|
||||
-- ensure all rows are closed
|
||||
iob_command <= CMD_PRECHARGE;
|
||||
iob_address(prefresh_cmd) <= '1'; -- all banks
|
||||
iob_bank <= (others => '0');
|
||||
elsif startup_refresh_count = startup_refresh_max-23 then
|
||||
-- these refreshes need to be at least tREF (66ns) apart
|
||||
iob_command <= CMD_REFRESH;
|
||||
elsif startup_refresh_count = startup_refresh_max-15 then
|
||||
iob_command <= CMD_REFRESH;
|
||||
elsif startup_refresh_count = startup_refresh_max-7 then
|
||||
-- Now load the mode register
|
||||
iob_command <= CMD_LOAD_MODE_REG;
|
||||
iob_address <= MODE_REG;
|
||||
end if;
|
||||
|
||||
------------------------------------------------------
|
||||
-- if startup is coomplete then go into idle mode,
|
||||
-- get prepared to accept a new command, and schedule
|
||||
-- the first refresh cycle
|
||||
------------------------------------------------------
|
||||
if startup_refresh_count = 0 then
|
||||
state <= s_idle;
|
||||
ready_for_new <= '1';
|
||||
got_transaction <= '0';
|
||||
startup_refresh_count <= to_unsigned(2048 - cycles_per_refresh+1,14);
|
||||
end if;
|
||||
|
||||
when s_idle_in_6 => state <= s_idle_in_5;
|
||||
when s_idle_in_5 => state <= s_idle_in_4;
|
||||
when s_idle_in_4 => state <= s_idle_in_3;
|
||||
when s_idle_in_3 => state <= s_idle_in_2;
|
||||
when s_idle_in_2 => state <= s_idle_in_1;
|
||||
when s_idle_in_1 => state <= s_idle;
|
||||
|
||||
when s_idle =>
|
||||
-- Priority is to issue a refresh if one is outstanding
|
||||
if pending_refresh = '1' or forcing_refresh = '1' then
|
||||
------------------------------------------------------------------------
|
||||
-- Start the refresh cycle.
|
||||
-- This tasks tRFC (66ns), so 6 idle cycles are needed @ 100MHz
|
||||
------------------------------------------------------------------------
|
||||
state <= s_idle_in_6;
|
||||
iob_command <= CMD_REFRESH;
|
||||
startup_refresh_count <= startup_refresh_count - cycles_per_refresh+1;
|
||||
elsif got_transaction = '1' then
|
||||
--------------------------------
|
||||
-- Start the read or write cycle.
|
||||
-- First task is to open the row
|
||||
--------------------------------
|
||||
state <= s_open_in_2;
|
||||
iob_command <= CMD_ACTIVE;
|
||||
iob_address <= save_row;
|
||||
iob_bank <= save_bank;
|
||||
end if;
|
||||
|
||||
--------------------------------------------
|
||||
-- Opening the row ready for reads or writes
|
||||
--------------------------------------------
|
||||
when s_open_in_2 => state <= s_open_in_1;
|
||||
|
||||
when s_open_in_1 =>
|
||||
-- still waiting for row to open
|
||||
if save_wr = '1' then
|
||||
state <= s_write_1;
|
||||
iob_dq_hiz <= '0';
|
||||
iob_data <= save_data_in(15 downto 0); -- get the DQ bus out of HiZ early
|
||||
else
|
||||
iob_dq_hiz <= '1';
|
||||
state <= s_read_1;
|
||||
end if;
|
||||
-- we will be ready for a new transaction next cycle!
|
||||
ready_for_new <= '1';
|
||||
got_transaction <= '0';
|
||||
|
||||
----------------------------------
|
||||
-- Processing the read transaction
|
||||
----------------------------------
|
||||
when s_read_1 =>
|
||||
state <= s_read_2;
|
||||
iob_command <= CMD_READ;
|
||||
iob_address <= save_col;
|
||||
iob_bank <= save_bank;
|
||||
iob_address(prefresh_cmd) <= '0'; -- A10 actually matters - it selects auto precharge
|
||||
|
||||
-- Schedule reading the data values off the bus
|
||||
data_ready_delay(data_ready_delay'high) <= '1';
|
||||
|
||||
-- Set the data masks to read all bytes
|
||||
iob_dqm <= (others => '0');
|
||||
dqm_sr(1 downto 0) <= (others => '0');
|
||||
|
||||
when s_read_2 =>
|
||||
state <= s_read_3;
|
||||
if forcing_refresh = '0' and got_transaction = '1' and can_back_to_back = '1' then
|
||||
if save_wr = '0' then
|
||||
state <= s_read_1;
|
||||
ready_for_new <= '1'; -- we will be ready for a new transaction next cycle!
|
||||
end if;
|
||||
end if;
|
||||
|
||||
when s_read_3 =>
|
||||
state <= s_read_4;
|
||||
if forcing_refresh = '0' and got_transaction = '1' and can_back_to_back = '1' then
|
||||
if save_wr = '0' then
|
||||
state <= s_read_1;
|
||||
ready_for_new <= '1'; -- we will be ready for a new transaction next cycle!
|
||||
end if;
|
||||
end if;
|
||||
|
||||
when s_read_4 =>
|
||||
state <= s_precharge;
|
||||
-- can we do back-to-back read?
|
||||
if forcing_refresh = '0' and got_transaction = '1' and can_back_to_back = '1' then
|
||||
if save_wr = '0' then
|
||||
state <= s_read_1;
|
||||
ready_for_new <= '1'; -- we will be ready for a new transaction next cycle!
|
||||
else
|
||||
state <= s_open_in_2; -- we have to wait for the read data to come back before we swutch the bus into HiZ
|
||||
end if;
|
||||
end if;
|
||||
|
||||
------------------------------------------------------------------
|
||||
-- Processing the write transaction
|
||||
-------------------------------------------------------------------
|
||||
when s_write_1 =>
|
||||
state <= s_write_2;
|
||||
iob_command <= CMD_WRITE;
|
||||
iob_address <= save_col;
|
||||
iob_address(prefresh_cmd) <= '0'; -- A10 actually matters - it selects auto precharge
|
||||
iob_bank <= save_bank;
|
||||
iob_dqm <= NOT save_byte_enable(1 downto 0);
|
||||
dqm_sr(1 downto 0) <= NOT save_byte_enable(3 downto 2);
|
||||
iob_data <= save_data_in(15 downto 0);
|
||||
iob_data_next <= save_data_in(31 downto 16);
|
||||
|
||||
when s_write_2 =>
|
||||
state <= s_write_3;
|
||||
iob_data <= iob_data_next;
|
||||
-- can we do a back-to-back write?
|
||||
if forcing_refresh = '0' and got_transaction = '1' and can_back_to_back = '1' then
|
||||
if save_wr = '1' then
|
||||
-- back-to-back write?
|
||||
state <= s_write_1;
|
||||
ready_for_new <= '1';
|
||||
got_transaction <= '0';
|
||||
end if;
|
||||
-- Although it looks right in simulation you can't go write-to-read
|
||||
-- here due to bus contention, as iob_dq_hiz takes a few ns.
|
||||
end if;
|
||||
|
||||
when s_write_3 => -- must wait tRDL, hence the extra idle state
|
||||
-- back to back transaction?
|
||||
if forcing_refresh = '0' and got_transaction = '1' and can_back_to_back = '1' then
|
||||
if save_wr = '1' then
|
||||
-- back-to-back write?
|
||||
state <= s_write_1;
|
||||
ready_for_new <= '1';
|
||||
got_transaction <= '0';
|
||||
else
|
||||
-- write-to-read switch?
|
||||
state <= s_read_1;
|
||||
iob_dq_hiz <= '1';
|
||||
ready_for_new <= '1'; -- we will be ready for a new transaction next cycle!
|
||||
got_transaction <= '0';
|
||||
end if;
|
||||
else
|
||||
iob_dq_hiz <= '1';
|
||||
state <= s_precharge;
|
||||
end if;
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Closing the row off (this closes all banks)
|
||||
-------------------------------------------------------------------
|
||||
when s_precharge =>
|
||||
state <= s_idle_in_3;
|
||||
iob_command <= CMD_PRECHARGE;
|
||||
iob_address(prefresh_cmd) <= '1'; -- A10 actually matters - it selects all banks or just one
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- We should never get here, but if we do then reset the memory
|
||||
-------------------------------------------------------------------
|
||||
when others =>
|
||||
state <= s_startup;
|
||||
ready_for_new <= '0';
|
||||
startup_refresh_count <= startup_refresh_max-to_unsigned(sdram_startup_cycles,14);
|
||||
end case;
|
||||
|
||||
if reset = '1' then -- Sync reset
|
||||
state <= s_startup;
|
||||
ready_for_new <= '0';
|
||||
startup_refresh_count <= startup_refresh_max-to_unsigned(sdram_startup_cycles,14);
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
end Behavioral;
|
||||
88
vhdl/SSRAM.vhd
Normal file
88
vhdl/SSRAM.vhd
Normal file
@ -0,0 +1,88 @@
|
||||
--
|
||||
-- Inferrable Synchronous SRAM for XST synthesis
|
||||
--
|
||||
-- Version : 0220
|
||||
--
|
||||
-- Copyright (c) 2002 Daniel Wallner (jesus@opencores.org)
|
||||
--
|
||||
-- All rights reserved
|
||||
--
|
||||
-- Redistribution and use in source and synthezised forms, with or without
|
||||
-- modification, are permitted provided that the following conditions are met:
|
||||
--
|
||||
-- Redistributions of source code must retain the above copyright notice,
|
||||
-- this list of conditions and the following disclaimer.
|
||||
--
|
||||
-- Redistributions in synthesized form must reproduce the above copyright
|
||||
-- notice, this list of conditions and the following disclaimer in the
|
||||
-- documentation and/or other materials provided with the distribution.
|
||||
--
|
||||
-- Neither the name of the author nor the names of other contributors may
|
||||
-- be used to endorse or promote products derived from this software without
|
||||
-- specific prior written permission.
|
||||
--
|
||||
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
|
||||
-- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
-- POSSIBILITY OF SUCH DAMAGE.
|
||||
--
|
||||
-- Please report bugs to the author, but before you do so, please
|
||||
-- make sure that this is not a derivative work and that
|
||||
-- you have the latest version of this file.
|
||||
--
|
||||
-- The latest version of this file can be found at:
|
||||
-- http://www.opencores.org/cvsweb.shtml/t51/
|
||||
--
|
||||
-- Limitations :
|
||||
--
|
||||
-- File history :
|
||||
-- 0208 : Initial release
|
||||
-- 0218 : Fixed data out at write
|
||||
-- 0220 : Added support for XST
|
||||
|
||||
library IEEE;
|
||||
use IEEE.std_logic_1164.all;
|
||||
use IEEE.numeric_std.all;
|
||||
|
||||
entity SSRAM is
|
||||
generic(
|
||||
AddrWidth : integer := 11;
|
||||
DataWidth : integer := 8
|
||||
);
|
||||
port(
|
||||
clk : in std_logic;
|
||||
ce : in std_logic;
|
||||
we : in std_logic;
|
||||
A : in std_logic_vector(AddrWidth - 1 downto 0);
|
||||
DIn : in std_logic_vector(DataWidth - 1 downto 0);
|
||||
DOut : out std_logic_vector(DataWidth - 1 downto 0)
|
||||
);
|
||||
end SSRAM;
|
||||
|
||||
architecture behaviour of SSRAM is
|
||||
|
||||
type Memory_Image is array (natural range <>) of std_logic_vector(DataWidth - 1 downto 0);
|
||||
signal RAM : Memory_Image(0 to 2 ** AddrWidth - 1);
|
||||
signal A_r : std_logic_vector(AddrWidth - 1 downto 0);
|
||||
|
||||
begin
|
||||
|
||||
process (Clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
if (ce = '1' and we = '1') then
|
||||
RAM(to_integer(unsigned(A))) <= DIn;
|
||||
end if;
|
||||
A_r <= A;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
DOut <= RAM(to_integer(unsigned(A_r)));
|
||||
end;
|
||||
1094
vhdl/T80.vhd
Normal file
1094
vhdl/T80.vhd
Normal file
File diff suppressed because it is too large
Load Diff
371
vhdl/T80_ALU.vhd
Normal file
371
vhdl/T80_ALU.vhd
Normal file
@ -0,0 +1,371 @@
|
||||
-- ****
|
||||
-- T80(b) core. In an effort to merge and maintain bug fixes ....
|
||||
--
|
||||
--
|
||||
-- Ver 301 parity flag is just parity for 8080, also overflow for Z80, by Sean Riddle
|
||||
-- Ver 300 started tidyup
|
||||
-- MikeJ March 2005
|
||||
-- Latest version from www.fpgaarcade.com (original www.opencores.org)
|
||||
--
|
||||
-- ****
|
||||
--
|
||||
-- Z80 compatible microprocessor core
|
||||
--
|
||||
-- Version : 0247
|
||||
--
|
||||
-- Copyright (c) 2001-2002 Daniel Wallner (jesus@opencores.org)
|
||||
--
|
||||
-- All rights reserved
|
||||
--
|
||||
-- Redistribution and use in source and synthezised forms, with or without
|
||||
-- modification, are permitted provided that the following conditions are met:
|
||||
--
|
||||
-- Redistributions of source code must retain the above copyright notice,
|
||||
-- this list of conditions and the following disclaimer.
|
||||
--
|
||||
-- Redistributions in synthesized form must reproduce the above copyright
|
||||
-- notice, this list of conditions and the following disclaimer in the
|
||||
-- documentation and/or other materials provided with the distribution.
|
||||
--
|
||||
-- Neither the name of the author nor the names of other contributors may
|
||||
-- be used to endorse or promote products derived from this software without
|
||||
-- specific prior written permission.
|
||||
--
|
||||
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
|
||||
-- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
-- POSSIBILITY OF SUCH DAMAGE.
|
||||
--
|
||||
-- Please report bugs to the author, but before you do so, please
|
||||
-- make sure that this is not a derivative work and that
|
||||
-- you have the latest version of this file.
|
||||
--
|
||||
-- The latest version of this file can be found at:
|
||||
-- http://www.opencores.org/cvsweb.shtml/t80/
|
||||
--
|
||||
-- Limitations :
|
||||
--
|
||||
-- File history :
|
||||
--
|
||||
-- 0214 : Fixed mostly flags, only the block instructions now fail the zex regression test
|
||||
--
|
||||
-- 0238 : Fixed zero flag for 16 bit SBC and ADC
|
||||
--
|
||||
-- 0240 : Added GB operations
|
||||
--
|
||||
-- 0242 : Cleanup
|
||||
--
|
||||
-- 0247 : Cleanup
|
||||
--
|
||||
|
||||
library IEEE;
|
||||
use IEEE.std_logic_1164.all;
|
||||
use IEEE.numeric_std.all;
|
||||
|
||||
entity T80_ALU is
|
||||
generic(
|
||||
Mode : integer := 0;
|
||||
Flag_C : integer := 0;
|
||||
Flag_N : integer := 1;
|
||||
Flag_P : integer := 2;
|
||||
Flag_X : integer := 3;
|
||||
Flag_H : integer := 4;
|
||||
Flag_Y : integer := 5;
|
||||
Flag_Z : integer := 6;
|
||||
Flag_S : integer := 7
|
||||
);
|
||||
port(
|
||||
Arith16 : in std_logic;
|
||||
Z16 : in std_logic;
|
||||
ALU_Op : in std_logic_vector(3 downto 0);
|
||||
IR : in std_logic_vector(5 downto 0);
|
||||
ISet : in std_logic_vector(1 downto 0);
|
||||
BusA : in std_logic_vector(7 downto 0);
|
||||
BusB : in std_logic_vector(7 downto 0);
|
||||
F_In : in std_logic_vector(7 downto 0);
|
||||
Q : out std_logic_vector(7 downto 0);
|
||||
F_Out : out std_logic_vector(7 downto 0)
|
||||
);
|
||||
end T80_ALU;
|
||||
|
||||
architecture rtl of T80_ALU is
|
||||
|
||||
procedure AddSub(A : std_logic_vector;
|
||||
B : std_logic_vector;
|
||||
Sub : std_logic;
|
||||
Carry_In : std_logic;
|
||||
signal Res : out std_logic_vector;
|
||||
signal Carry : out std_logic) is
|
||||
|
||||
variable B_i : unsigned(A'length - 1 downto 0);
|
||||
variable Res_i : unsigned(A'length + 1 downto 0);
|
||||
begin
|
||||
if Sub = '1' then
|
||||
B_i := not unsigned(B);
|
||||
else
|
||||
B_i := unsigned(B);
|
||||
end if;
|
||||
|
||||
Res_i := unsigned("0" & A & Carry_In) + unsigned("0" & B_i & "1");
|
||||
Carry <= Res_i(A'length + 1);
|
||||
Res <= std_logic_vector(Res_i(A'length downto 1));
|
||||
end;
|
||||
|
||||
-- AddSub variables (temporary signals)
|
||||
signal UseCarry : std_logic;
|
||||
signal Carry7_v : std_logic;
|
||||
signal Overflow_v : std_logic;
|
||||
signal HalfCarry_v : std_logic;
|
||||
signal Carry_v : std_logic;
|
||||
signal Q_v : std_logic_vector(7 downto 0);
|
||||
|
||||
signal BitMask : std_logic_vector(7 downto 0);
|
||||
|
||||
begin
|
||||
|
||||
with IR(5 downto 3) select BitMask <= "00000001" when "000",
|
||||
"00000010" when "001",
|
||||
"00000100" when "010",
|
||||
"00001000" when "011",
|
||||
"00010000" when "100",
|
||||
"00100000" when "101",
|
||||
"01000000" when "110",
|
||||
"10000000" when others;
|
||||
|
||||
UseCarry <= not ALU_Op(2) and ALU_Op(0);
|
||||
AddSub(BusA(3 downto 0), BusB(3 downto 0), ALU_Op(1), ALU_Op(1) xor (UseCarry and F_In(Flag_C)), Q_v(3 downto 0), HalfCarry_v);
|
||||
AddSub(BusA(6 downto 4), BusB(6 downto 4), ALU_Op(1), HalfCarry_v, Q_v(6 downto 4), Carry7_v);
|
||||
AddSub(BusA(7 downto 7), BusB(7 downto 7), ALU_Op(1), Carry7_v, Q_v(7 downto 7), Carry_v);
|
||||
|
||||
-- bug fix - parity flag is just parity for 8080, also overflow for Z80
|
||||
process (Carry_v, Carry7_v, Q_v)
|
||||
begin
|
||||
if(Mode=2) then
|
||||
OverFlow_v <= not (Q_v(0) xor Q_v(1) xor Q_v(2) xor Q_v(3) xor
|
||||
Q_v(4) xor Q_v(5) xor Q_v(6) xor Q_v(7)); else
|
||||
OverFlow_v <= Carry_v xor Carry7_v;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
process (Arith16, ALU_OP, F_In, BusA, BusB, IR, Q_v, Carry_v, HalfCarry_v, OverFlow_v, BitMask, ISet, Z16)
|
||||
variable Q_t : std_logic_vector(7 downto 0);
|
||||
variable DAA_Q : unsigned(8 downto 0);
|
||||
begin
|
||||
Q_t := "--------";
|
||||
F_Out <= F_In;
|
||||
DAA_Q := "---------";
|
||||
case ALU_Op is
|
||||
when "0000" | "0001" | "0010" | "0011" | "0100" | "0101" | "0110" | "0111" =>
|
||||
F_Out(Flag_N) <= '0';
|
||||
F_Out(Flag_C) <= '0';
|
||||
case ALU_OP(2 downto 0) is
|
||||
when "000" | "001" => -- ADD, ADC
|
||||
Q_t := Q_v;
|
||||
F_Out(Flag_C) <= Carry_v;
|
||||
F_Out(Flag_H) <= HalfCarry_v;
|
||||
F_Out(Flag_P) <= OverFlow_v;
|
||||
when "010" | "011" | "111" => -- SUB, SBC, CP
|
||||
Q_t := Q_v;
|
||||
F_Out(Flag_N) <= '1';
|
||||
F_Out(Flag_C) <= not Carry_v;
|
||||
F_Out(Flag_H) <= not HalfCarry_v;
|
||||
F_Out(Flag_P) <= OverFlow_v;
|
||||
when "100" => -- AND
|
||||
Q_t(7 downto 0) := BusA and BusB;
|
||||
F_Out(Flag_H) <= '1';
|
||||
when "101" => -- XOR
|
||||
Q_t(7 downto 0) := BusA xor BusB;
|
||||
F_Out(Flag_H) <= '0';
|
||||
when others => -- OR "110"
|
||||
Q_t(7 downto 0) := BusA or BusB;
|
||||
F_Out(Flag_H) <= '0';
|
||||
end case;
|
||||
if ALU_Op(2 downto 0) = "111" then -- CP
|
||||
F_Out(Flag_X) <= BusB(3);
|
||||
F_Out(Flag_Y) <= BusB(5);
|
||||
else
|
||||
F_Out(Flag_X) <= Q_t(3);
|
||||
F_Out(Flag_Y) <= Q_t(5);
|
||||
end if;
|
||||
if Q_t(7 downto 0) = "00000000" then
|
||||
F_Out(Flag_Z) <= '1';
|
||||
if Z16 = '1' then
|
||||
F_Out(Flag_Z) <= F_In(Flag_Z); -- 16 bit ADC,SBC
|
||||
end if;
|
||||
else
|
||||
F_Out(Flag_Z) <= '0';
|
||||
end if;
|
||||
F_Out(Flag_S) <= Q_t(7);
|
||||
case ALU_Op(2 downto 0) is
|
||||
when "000" | "001" | "010" | "011" | "111" => -- ADD, ADC, SUB, SBC, CP
|
||||
when others =>
|
||||
F_Out(Flag_P) <= not (Q_t(0) xor Q_t(1) xor Q_t(2) xor Q_t(3) xor
|
||||
Q_t(4) xor Q_t(5) xor Q_t(6) xor Q_t(7));
|
||||
end case;
|
||||
if Arith16 = '1' then
|
||||
F_Out(Flag_S) <= F_In(Flag_S);
|
||||
F_Out(Flag_Z) <= F_In(Flag_Z);
|
||||
F_Out(Flag_P) <= F_In(Flag_P);
|
||||
end if;
|
||||
when "1100" =>
|
||||
-- DAA
|
||||
F_Out(Flag_H) <= F_In(Flag_H);
|
||||
F_Out(Flag_C) <= F_In(Flag_C);
|
||||
DAA_Q(7 downto 0) := unsigned(BusA);
|
||||
DAA_Q(8) := '0';
|
||||
if F_In(Flag_N) = '0' then
|
||||
-- After addition
|
||||
-- Alow > 9 or H = 1
|
||||
if DAA_Q(3 downto 0) > 9 or F_In(Flag_H) = '1' then
|
||||
if (DAA_Q(3 downto 0) > 9) then
|
||||
F_Out(Flag_H) <= '1';
|
||||
else
|
||||
F_Out(Flag_H) <= '0';
|
||||
end if;
|
||||
DAA_Q := DAA_Q + 6;
|
||||
end if;
|
||||
-- new Ahigh > 9 or C = 1
|
||||
if DAA_Q(8 downto 4) > 9 or F_In(Flag_C) = '1' then
|
||||
DAA_Q := DAA_Q + 96; -- 0x60
|
||||
end if;
|
||||
else
|
||||
-- After subtraction
|
||||
if DAA_Q(3 downto 0) > 9 or F_In(Flag_H) = '1' then
|
||||
if DAA_Q(3 downto 0) > 5 then
|
||||
F_Out(Flag_H) <= '0';
|
||||
end if;
|
||||
DAA_Q(7 downto 0) := DAA_Q(7 downto 0) - 6;
|
||||
end if;
|
||||
if unsigned(BusA) > 153 or F_In(Flag_C) = '1' then
|
||||
DAA_Q := DAA_Q - 352; -- 0x160
|
||||
end if;
|
||||
end if;
|
||||
F_Out(Flag_X) <= DAA_Q(3);
|
||||
F_Out(Flag_Y) <= DAA_Q(5);
|
||||
F_Out(Flag_C) <= F_In(Flag_C) or DAA_Q(8);
|
||||
Q_t := std_logic_vector(DAA_Q(7 downto 0));
|
||||
if DAA_Q(7 downto 0) = "00000000" then
|
||||
F_Out(Flag_Z) <= '1';
|
||||
else
|
||||
F_Out(Flag_Z) <= '0';
|
||||
end if;
|
||||
F_Out(Flag_S) <= DAA_Q(7);
|
||||
F_Out(Flag_P) <= not (DAA_Q(0) xor DAA_Q(1) xor DAA_Q(2) xor DAA_Q(3) xor
|
||||
DAA_Q(4) xor DAA_Q(5) xor DAA_Q(6) xor DAA_Q(7));
|
||||
when "1101" | "1110" =>
|
||||
-- RLD, RRD
|
||||
Q_t(7 downto 4) := BusA(7 downto 4);
|
||||
if ALU_Op(0) = '1' then
|
||||
Q_t(3 downto 0) := BusB(7 downto 4);
|
||||
else
|
||||
Q_t(3 downto 0) := BusB(3 downto 0);
|
||||
end if;
|
||||
F_Out(Flag_H) <= '0';
|
||||
F_Out(Flag_N) <= '0';
|
||||
F_Out(Flag_X) <= Q_t(3);
|
||||
F_Out(Flag_Y) <= Q_t(5);
|
||||
if Q_t(7 downto 0) = "00000000" then
|
||||
F_Out(Flag_Z) <= '1';
|
||||
else
|
||||
F_Out(Flag_Z) <= '0';
|
||||
end if;
|
||||
F_Out(Flag_S) <= Q_t(7);
|
||||
F_Out(Flag_P) <= not (Q_t(0) xor Q_t(1) xor Q_t(2) xor Q_t(3) xor
|
||||
Q_t(4) xor Q_t(5) xor Q_t(6) xor Q_t(7));
|
||||
when "1001" =>
|
||||
-- BIT
|
||||
Q_t(7 downto 0) := BusB and BitMask;
|
||||
F_Out(Flag_S) <= Q_t(7);
|
||||
if Q_t(7 downto 0) = "00000000" then
|
||||
F_Out(Flag_Z) <= '1';
|
||||
F_Out(Flag_P) <= '1';
|
||||
else
|
||||
F_Out(Flag_Z) <= '0';
|
||||
F_Out(Flag_P) <= '0';
|
||||
end if;
|
||||
F_Out(Flag_H) <= '1';
|
||||
F_Out(Flag_N) <= '0';
|
||||
F_Out(Flag_X) <= '0';
|
||||
F_Out(Flag_Y) <= '0';
|
||||
if IR(2 downto 0) /= "110" then
|
||||
F_Out(Flag_X) <= BusB(3);
|
||||
F_Out(Flag_Y) <= BusB(5);
|
||||
end if;
|
||||
when "1010" =>
|
||||
-- SET
|
||||
Q_t(7 downto 0) := BusB or BitMask;
|
||||
when "1011" =>
|
||||
-- RES
|
||||
Q_t(7 downto 0) := BusB and not BitMask;
|
||||
when "1000" =>
|
||||
-- ROT
|
||||
case IR(5 downto 3) is
|
||||
when "000" => -- RLC
|
||||
Q_t(7 downto 1) := BusA(6 downto 0);
|
||||
Q_t(0) := BusA(7);
|
||||
F_Out(Flag_C) <= BusA(7);
|
||||
when "010" => -- RL
|
||||
Q_t(7 downto 1) := BusA(6 downto 0);
|
||||
Q_t(0) := F_In(Flag_C);
|
||||
F_Out(Flag_C) <= BusA(7);
|
||||
when "001" => -- RRC
|
||||
Q_t(6 downto 0) := BusA(7 downto 1);
|
||||
Q_t(7) := BusA(0);
|
||||
F_Out(Flag_C) <= BusA(0);
|
||||
when "011" => -- RR
|
||||
Q_t(6 downto 0) := BusA(7 downto 1);
|
||||
Q_t(7) := F_In(Flag_C);
|
||||
F_Out(Flag_C) <= BusA(0);
|
||||
when "100" => -- SLA
|
||||
Q_t(7 downto 1) := BusA(6 downto 0);
|
||||
Q_t(0) := '0';
|
||||
F_Out(Flag_C) <= BusA(7);
|
||||
when "110" => -- SLL (Undocumented) / SWAP
|
||||
if Mode = 3 then
|
||||
Q_t(7 downto 4) := BusA(3 downto 0);
|
||||
Q_t(3 downto 0) := BusA(7 downto 4);
|
||||
F_Out(Flag_C) <= '0';
|
||||
else
|
||||
Q_t(7 downto 1) := BusA(6 downto 0);
|
||||
Q_t(0) := '1';
|
||||
F_Out(Flag_C) <= BusA(7);
|
||||
end if;
|
||||
when "101" => -- SRA
|
||||
Q_t(6 downto 0) := BusA(7 downto 1);
|
||||
Q_t(7) := BusA(7);
|
||||
F_Out(Flag_C) <= BusA(0);
|
||||
when others => -- SRL
|
||||
Q_t(6 downto 0) := BusA(7 downto 1);
|
||||
Q_t(7) := '0';
|
||||
F_Out(Flag_C) <= BusA(0);
|
||||
end case;
|
||||
F_Out(Flag_H) <= '0';
|
||||
F_Out(Flag_N) <= '0';
|
||||
F_Out(Flag_X) <= Q_t(3);
|
||||
F_Out(Flag_Y) <= Q_t(5);
|
||||
F_Out(Flag_S) <= Q_t(7);
|
||||
if Q_t(7 downto 0) = "00000000" then
|
||||
F_Out(Flag_Z) <= '1';
|
||||
else
|
||||
F_Out(Flag_Z) <= '0';
|
||||
end if;
|
||||
F_Out(Flag_P) <= not (Q_t(0) xor Q_t(1) xor Q_t(2) xor Q_t(3) xor
|
||||
Q_t(4) xor Q_t(5) xor Q_t(6) xor Q_t(7));
|
||||
if ISet = "00" then
|
||||
F_Out(Flag_P) <= F_In(Flag_P);
|
||||
F_Out(Flag_S) <= F_In(Flag_S);
|
||||
F_Out(Flag_Z) <= F_In(Flag_Z);
|
||||
end if;
|
||||
when others =>
|
||||
null;
|
||||
end case;
|
||||
Q <= Q_t;
|
||||
end process;
|
||||
end;
|
||||
2028
vhdl/T80_MCode.vhd
Normal file
2028
vhdl/T80_MCode.vhd
Normal file
File diff suppressed because it is too large
Load Diff
220
vhdl/T80_Pack.vhd
Normal file
220
vhdl/T80_Pack.vhd
Normal file
@ -0,0 +1,220 @@
|
||||
-- ****
|
||||
-- T80(b) core. In an effort to merge and maintain bug fixes ....
|
||||
--
|
||||
--
|
||||
-- Ver 303 add undocumented DDCB and FDCB opcodes by TobiFlex 20.04.2010
|
||||
-- Ver 300 started tidyup
|
||||
-- MikeJ March 2005
|
||||
-- Latest version from www.fpgaarcade.com (original www.opencores.org)
|
||||
--
|
||||
-- ****
|
||||
--
|
||||
-- Z80 compatible microprocessor core
|
||||
--
|
||||
-- Version : 0242
|
||||
--
|
||||
-- Copyright (c) 2001-2002 Daniel Wallner (jesus@opencores.org)
|
||||
--
|
||||
-- All rights reserved
|
||||
--
|
||||
-- Redistribution and use in source and synthezised forms, with or without
|
||||
-- modification, are permitted provided that the following conditions are met:
|
||||
--
|
||||
-- Redistributions of source code must retain the above copyright notice,
|
||||
-- this list of conditions and the following disclaimer.
|
||||
--
|
||||
-- Redistributions in synthesized form must reproduce the above copyright
|
||||
-- notice, this list of conditions and the following disclaimer in the
|
||||
-- documentation and/or other materials provided with the distribution.
|
||||
--
|
||||
-- Neither the name of the author nor the names of other contributors may
|
||||
-- be used to endorse or promote products derived from this software without
|
||||
-- specific prior written permission.
|
||||
--
|
||||
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
|
||||
-- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
-- POSSIBILITY OF SUCH DAMAGE.
|
||||
--
|
||||
-- Please report bugs to the author, but before you do so, please
|
||||
-- make sure that this is not a derivative work and that
|
||||
-- you have the latest version of this file.
|
||||
--
|
||||
-- The latest version of this file can be found at:
|
||||
-- http://www.opencores.org/cvsweb.shtml/t80/
|
||||
--
|
||||
-- Limitations :
|
||||
--
|
||||
-- File history :
|
||||
--
|
||||
|
||||
library IEEE;
|
||||
use IEEE.std_logic_1164.all;
|
||||
|
||||
package T80_Pack is
|
||||
|
||||
component T80
|
||||
generic(
|
||||
Mode : integer := 0; -- 0 => Z80, 1 => Fast Z80, 2 => 8080, 3 => GB
|
||||
IOWait : integer := 0; -- 1 => Single cycle I/O, 1 => Std I/O cycle
|
||||
Flag_C : integer := 0;
|
||||
Flag_N : integer := 1;
|
||||
Flag_P : integer := 2;
|
||||
Flag_X : integer := 3;
|
||||
Flag_H : integer := 4;
|
||||
Flag_Y : integer := 5;
|
||||
Flag_Z : integer := 6;
|
||||
Flag_S : integer := 7
|
||||
);
|
||||
port(
|
||||
RESET_n : in std_logic;
|
||||
CLK_n : in std_logic;
|
||||
CEN : in std_logic;
|
||||
WAIT_n : in std_logic;
|
||||
INT_n : in std_logic;
|
||||
NMI_n : in std_logic;
|
||||
BUSRQ_n : in std_logic;
|
||||
M1_n : out std_logic;
|
||||
IORQ : out std_logic;
|
||||
NoRead : out std_logic;
|
||||
Write : out std_logic;
|
||||
RFSH_n : out std_logic;
|
||||
HALT_n : out std_logic;
|
||||
BUSAK_n : out std_logic;
|
||||
A : out std_logic_vector(15 downto 0);
|
||||
DInst : in std_logic_vector(7 downto 0);
|
||||
DI : in std_logic_vector(7 downto 0);
|
||||
DO : out std_logic_vector(7 downto 0);
|
||||
MC : out std_logic_vector(2 downto 0);
|
||||
TS : out std_logic_vector(2 downto 0);
|
||||
IntCycle_n : out std_logic;
|
||||
IntE : out std_logic;
|
||||
Stop : out std_logic
|
||||
);
|
||||
end component;
|
||||
|
||||
component T80_Reg
|
||||
port(
|
||||
Clk : in std_logic;
|
||||
CEN : in std_logic;
|
||||
WEH : in std_logic;
|
||||
WEL : in std_logic;
|
||||
AddrA : in std_logic_vector(2 downto 0);
|
||||
AddrB : in std_logic_vector(2 downto 0);
|
||||
AddrC : in std_logic_vector(2 downto 0);
|
||||
DIH : in std_logic_vector(7 downto 0);
|
||||
DIL : in std_logic_vector(7 downto 0);
|
||||
DOAH : out std_logic_vector(7 downto 0);
|
||||
DOAL : out std_logic_vector(7 downto 0);
|
||||
DOBH : out std_logic_vector(7 downto 0);
|
||||
DOBL : out std_logic_vector(7 downto 0);
|
||||
DOCH : out std_logic_vector(7 downto 0);
|
||||
DOCL : out std_logic_vector(7 downto 0)
|
||||
);
|
||||
end component;
|
||||
|
||||
component T80_MCode
|
||||
generic(
|
||||
Mode : integer := 0;
|
||||
Flag_C : integer := 0;
|
||||
Flag_N : integer := 1;
|
||||
Flag_P : integer := 2;
|
||||
Flag_X : integer := 3;
|
||||
Flag_H : integer := 4;
|
||||
Flag_Y : integer := 5;
|
||||
Flag_Z : integer := 6;
|
||||
Flag_S : integer := 7
|
||||
);
|
||||
port(
|
||||
IR : in std_logic_vector(7 downto 0);
|
||||
ISet : in std_logic_vector(1 downto 0);
|
||||
MCycle : in std_logic_vector(2 downto 0);
|
||||
F : in std_logic_vector(7 downto 0);
|
||||
NMICycle : in std_logic;
|
||||
IntCycle : in std_logic;
|
||||
XY_State : in std_logic_vector(1 downto 0);
|
||||
MCycles : out std_logic_vector(2 downto 0);
|
||||
TStates : out std_logic_vector(2 downto 0);
|
||||
Prefix : out std_logic_vector(1 downto 0); -- None,BC,ED,DD/FD
|
||||
Inc_PC : out std_logic;
|
||||
Inc_WZ : out std_logic;
|
||||
IncDec_16 : out std_logic_vector(3 downto 0); -- BC,DE,HL,SP 0 is inc
|
||||
Read_To_Reg : out std_logic;
|
||||
Read_To_Acc : out std_logic;
|
||||
Set_BusA_To : out std_logic_vector(3 downto 0); -- B,C,D,E,H,L,DI/DB,A,SP(L),SP(M),0,F
|
||||
Set_BusB_To : out std_logic_vector(3 downto 0); -- B,C,D,E,H,L,DI,A,SP(L),SP(M),1,F,PC(L),PC(M),0
|
||||
ALU_Op : out std_logic_vector(3 downto 0);
|
||||
-- ADD, ADC, SUB, SBC, AND, XOR, OR, CP, ROT, BIT, SET, RES, DAA, RLD, RRD, None
|
||||
Save_ALU : out std_logic;
|
||||
PreserveC : out std_logic;
|
||||
Arith16 : out std_logic;
|
||||
Set_Addr_To : out std_logic_vector(2 downto 0); -- aNone,aXY,aIOA,aSP,aBC,aDE,aZI
|
||||
IORQ : out std_logic;
|
||||
Jump : out std_logic;
|
||||
JumpE : out std_logic;
|
||||
JumpXY : out std_logic;
|
||||
Call : out std_logic;
|
||||
RstP : out std_logic;
|
||||
LDZ : out std_logic;
|
||||
LDW : out std_logic;
|
||||
LDSPHL : out std_logic;
|
||||
Special_LD : out std_logic_vector(2 downto 0); -- A,I;A,R;I,A;R,A;None
|
||||
ExchangeDH : out std_logic;
|
||||
ExchangeRp : out std_logic;
|
||||
ExchangeAF : out std_logic;
|
||||
ExchangeRS : out std_logic;
|
||||
I_DJNZ : out std_logic;
|
||||
I_CPL : out std_logic;
|
||||
I_CCF : out std_logic;
|
||||
I_SCF : out std_logic;
|
||||
I_RETN : out std_logic;
|
||||
I_BT : out std_logic;
|
||||
I_BC : out std_logic;
|
||||
I_BTR : out std_logic;
|
||||
I_RLD : out std_logic;
|
||||
I_RRD : out std_logic;
|
||||
I_INRC : out std_logic;
|
||||
SetDI : out std_logic;
|
||||
SetEI : out std_logic;
|
||||
IMode : out std_logic_vector(1 downto 0);
|
||||
Halt : out std_logic;
|
||||
NoRead : out std_logic;
|
||||
Write : out std_logic;
|
||||
XYbit_undoc : out std_logic
|
||||
);
|
||||
end component;
|
||||
|
||||
component T80_ALU
|
||||
generic(
|
||||
Mode : integer := 0;
|
||||
Flag_C : integer := 0;
|
||||
Flag_N : integer := 1;
|
||||
Flag_P : integer := 2;
|
||||
Flag_X : integer := 3;
|
||||
Flag_H : integer := 4;
|
||||
Flag_Y : integer := 5;
|
||||
Flag_Z : integer := 6;
|
||||
Flag_S : integer := 7
|
||||
);
|
||||
port(
|
||||
Arith16 : in std_logic;
|
||||
Z16 : in std_logic;
|
||||
ALU_Op : in std_logic_vector(3 downto 0);
|
||||
IR : in std_logic_vector(5 downto 0);
|
||||
ISet : in std_logic_vector(1 downto 0);
|
||||
BusA : in std_logic_vector(7 downto 0);
|
||||
BusB : in std_logic_vector(7 downto 0);
|
||||
F_In : in std_logic_vector(7 downto 0);
|
||||
Q : out std_logic_vector(7 downto 0);
|
||||
F_Out : out std_logic_vector(7 downto 0)
|
||||
);
|
||||
end component;
|
||||
|
||||
end;
|
||||
114
vhdl/T80_Reg.vhd
Normal file
114
vhdl/T80_Reg.vhd
Normal file
@ -0,0 +1,114 @@
|
||||
-- ****
|
||||
-- T80(b) core. In an effort to merge and maintain bug fixes ....
|
||||
--
|
||||
--
|
||||
-- Ver 300 started tidyup
|
||||
-- MikeJ March 2005
|
||||
-- Latest version from www.fpgaarcade.com (original www.opencores.org)
|
||||
--
|
||||
-- ****
|
||||
--
|
||||
-- T80 Registers, technology independent
|
||||
--
|
||||
-- Version : 0244
|
||||
--
|
||||
-- Copyright (c) 2002 Daniel Wallner (jesus@opencores.org)
|
||||
--
|
||||
-- All rights reserved
|
||||
--
|
||||
-- Redistribution and use in source and synthezised forms, with or without
|
||||
-- modification, are permitted provided that the following conditions are met:
|
||||
--
|
||||
-- Redistributions of source code must retain the above copyright notice,
|
||||
-- this list of conditions and the following disclaimer.
|
||||
--
|
||||
-- Redistributions in synthesized form must reproduce the above copyright
|
||||
-- notice, this list of conditions and the following disclaimer in the
|
||||
-- documentation and/or other materials provided with the distribution.
|
||||
--
|
||||
-- Neither the name of the author nor the names of other contributors may
|
||||
-- be used to endorse or promote products derived from this software without
|
||||
-- specific prior written permission.
|
||||
--
|
||||
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
|
||||
-- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
-- POSSIBILITY OF SUCH DAMAGE.
|
||||
--
|
||||
-- Please report bugs to the author, but before you do so, please
|
||||
-- make sure that this is not a derivative work and that
|
||||
-- you have the latest version of this file.
|
||||
--
|
||||
-- The latest version of this file can be found at:
|
||||
-- http://www.opencores.org/cvsweb.shtml/t51/
|
||||
--
|
||||
-- Limitations :
|
||||
--
|
||||
-- File history :
|
||||
--
|
||||
-- 0242 : Initial release
|
||||
--
|
||||
-- 0244 : Changed to single register file
|
||||
--
|
||||
|
||||
library IEEE;
|
||||
use IEEE.std_logic_1164.all;
|
||||
use IEEE.numeric_std.all;
|
||||
|
||||
entity T80_Reg is
|
||||
port(
|
||||
Clk : in std_logic;
|
||||
CEN : in std_logic;
|
||||
WEH : in std_logic;
|
||||
WEL : in std_logic;
|
||||
AddrA : in std_logic_vector(2 downto 0);
|
||||
AddrB : in std_logic_vector(2 downto 0);
|
||||
AddrC : in std_logic_vector(2 downto 0);
|
||||
DIH : in std_logic_vector(7 downto 0);
|
||||
DIL : in std_logic_vector(7 downto 0);
|
||||
DOAH : out std_logic_vector(7 downto 0);
|
||||
DOAL : out std_logic_vector(7 downto 0);
|
||||
DOBH : out std_logic_vector(7 downto 0);
|
||||
DOBL : out std_logic_vector(7 downto 0);
|
||||
DOCH : out std_logic_vector(7 downto 0);
|
||||
DOCL : out std_logic_vector(7 downto 0)
|
||||
);
|
||||
end T80_Reg;
|
||||
|
||||
architecture rtl of T80_Reg is
|
||||
|
||||
type Register_Image is array (natural range <>) of std_logic_vector(7 downto 0);
|
||||
signal RegsH : Register_Image(0 to 7);
|
||||
signal RegsL : Register_Image(0 to 7);
|
||||
|
||||
begin
|
||||
|
||||
process (Clk)
|
||||
begin
|
||||
if Clk'event and Clk = '1' then
|
||||
if CEN = '1' then
|
||||
if WEH = '1' then
|
||||
RegsH(to_integer(unsigned(AddrA))) <= DIH;
|
||||
end if;
|
||||
if WEL = '1' then
|
||||
RegsL(to_integer(unsigned(AddrA))) <= DIL;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
DOAH <= RegsH(to_integer(unsigned(AddrA)));
|
||||
DOAL <= RegsL(to_integer(unsigned(AddrA)));
|
||||
DOBH <= RegsH(to_integer(unsigned(AddrB)));
|
||||
DOBL <= RegsL(to_integer(unsigned(AddrB)));
|
||||
DOCH <= RegsH(to_integer(unsigned(AddrC)));
|
||||
DOCL <= RegsL(to_integer(unsigned(AddrC)));
|
||||
|
||||
end;
|
||||
192
vhdl/T80se.vhd
Normal file
192
vhdl/T80se.vhd
Normal file
@ -0,0 +1,192 @@
|
||||
-- ****
|
||||
-- T80(b) core. In an effort to merge and maintain bug fixes ....
|
||||
--
|
||||
--
|
||||
-- Ver 300 started tidyup
|
||||
-- MikeJ March 2005
|
||||
-- Latest version from www.fpgaarcade.com (original www.opencores.org)
|
||||
--
|
||||
-- ****
|
||||
--
|
||||
-- Z80 compatible microprocessor core, synchronous top level with clock enable
|
||||
-- Different timing than the original z80
|
||||
-- Inputs needs to be synchronous and outputs may glitch
|
||||
--
|
||||
-- Version : 0240
|
||||
--
|
||||
-- Copyright (c) 2001-2002 Daniel Wallner (jesus@opencores.org)
|
||||
--
|
||||
-- All rights reserved
|
||||
--
|
||||
-- Redistribution and use in source and synthezised forms, with or without
|
||||
-- modification, are permitted provided that the following conditions are met:
|
||||
--
|
||||
-- Redistributions of source code must retain the above copyright notice,
|
||||
-- this list of conditions and the following disclaimer.
|
||||
--
|
||||
-- Redistributions in synthesized form must reproduce the above copyright
|
||||
-- notice, this list of conditions and the following disclaimer in the
|
||||
-- documentation and/or other materials provided with the distribution.
|
||||
--
|
||||
-- Neither the name of the author nor the names of other contributors may
|
||||
-- be used to endorse or promote products derived from this software without
|
||||
-- specific prior written permission.
|
||||
--
|
||||
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
|
||||
-- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
-- POSSIBILITY OF SUCH DAMAGE.
|
||||
--
|
||||
-- Please report bugs to the author, but before you do so, please
|
||||
-- make sure that this is not a derivative work and that
|
||||
-- you have the latest version of this file.
|
||||
--
|
||||
-- The latest version of this file can be found at:
|
||||
-- http://www.opencores.org/cvsweb.shtml/t80/
|
||||
--
|
||||
-- Limitations :
|
||||
--
|
||||
-- File history :
|
||||
--
|
||||
-- 0235 : First release
|
||||
--
|
||||
-- 0236 : Added T2Write generic
|
||||
--
|
||||
-- 0237 : Fixed T2Write with wait state
|
||||
--
|
||||
-- 0238 : Updated for T80 interface change
|
||||
--
|
||||
-- 0240 : Updated for T80 interface change
|
||||
--
|
||||
-- 0242 : Updated for T80 interface change
|
||||
--
|
||||
library IEEE;
|
||||
use IEEE.std_logic_1164.all;
|
||||
use IEEE.numeric_std.all;
|
||||
use work.T80_Pack.all;
|
||||
|
||||
entity T80se is
|
||||
generic(
|
||||
Mode : integer := 0; -- 0 => Z80, 1 => Fast Z80, 2 => 8080, 3 => GB
|
||||
T2Write : integer := 0; -- 0 => WR_n active in T3, /=0 => WR_n active in T2
|
||||
IOWait : integer := 1 -- 0 => Single cycle I/O, 1 => Std I/O cycle
|
||||
);
|
||||
port(
|
||||
RESET_n : in std_logic;
|
||||
CLK_n : in std_logic;
|
||||
CLKEN : in std_logic;
|
||||
WAIT_n : in std_logic;
|
||||
INT_n : in std_logic;
|
||||
NMI_n : in std_logic;
|
||||
BUSRQ_n : in std_logic;
|
||||
M1_n : out std_logic;
|
||||
MREQ_n : out std_logic;
|
||||
IORQ_n : out std_logic;
|
||||
RD_n : out std_logic;
|
||||
WR_n : out std_logic;
|
||||
RFSH_n : out std_logic;
|
||||
HALT_n : out std_logic;
|
||||
BUSAK_n : out std_logic;
|
||||
A : out std_logic_vector(15 downto 0);
|
||||
DI : in std_logic_vector(7 downto 0);
|
||||
DO : out std_logic_vector(7 downto 0)
|
||||
);
|
||||
end T80se;
|
||||
|
||||
architecture rtl of T80se is
|
||||
|
||||
signal IntCycle_n : std_logic;
|
||||
signal NoRead : std_logic;
|
||||
signal Write : std_logic;
|
||||
signal IORQ : std_logic;
|
||||
signal DI_Reg : std_logic_vector(7 downto 0);
|
||||
signal MCycle : std_logic_vector(2 downto 0);
|
||||
signal TState : std_logic_vector(2 downto 0);
|
||||
|
||||
begin
|
||||
|
||||
u0 : T80
|
||||
generic map(
|
||||
Mode => Mode,
|
||||
IOWait => IOWait)
|
||||
port map(
|
||||
CEN => CLKEN,
|
||||
M1_n => M1_n,
|
||||
IORQ => IORQ,
|
||||
NoRead => NoRead,
|
||||
Write => Write,
|
||||
RFSH_n => RFSH_n,
|
||||
HALT_n => HALT_n,
|
||||
WAIT_n => Wait_n,
|
||||
INT_n => INT_n,
|
||||
NMI_n => NMI_n,
|
||||
RESET_n => RESET_n,
|
||||
BUSRQ_n => BUSRQ_n,
|
||||
BUSAK_n => BUSAK_n,
|
||||
CLK_n => CLK_n,
|
||||
A => A,
|
||||
DInst => DI,
|
||||
DI => DI_Reg,
|
||||
DO => DO,
|
||||
MC => MCycle,
|
||||
TS => TState,
|
||||
IntCycle_n => IntCycle_n);
|
||||
|
||||
process (RESET_n, CLK_n)
|
||||
begin
|
||||
if RESET_n = '0' then
|
||||
RD_n <= '1';
|
||||
WR_n <= '1';
|
||||
IORQ_n <= '1';
|
||||
MREQ_n <= '1';
|
||||
DI_Reg <= "00000000";
|
||||
elsif CLK_n'event and CLK_n = '1' then
|
||||
if CLKEN = '1' then
|
||||
RD_n <= '1';
|
||||
WR_n <= '1';
|
||||
IORQ_n <= '1';
|
||||
MREQ_n <= '1';
|
||||
if MCycle = "001" then
|
||||
if TState = "001" or (TState = "010" and Wait_n = '0') then
|
||||
RD_n <= not IntCycle_n;
|
||||
MREQ_n <= not IntCycle_n;
|
||||
IORQ_n <= IntCycle_n;
|
||||
end if;
|
||||
if TState = "011" then
|
||||
MREQ_n <= '0';
|
||||
end if;
|
||||
else
|
||||
if (TState = "001" or (TState = "010" and Wait_n = '0')) and NoRead = '0' and Write = '0' then
|
||||
RD_n <= '0';
|
||||
IORQ_n <= not IORQ;
|
||||
MREQ_n <= IORQ;
|
||||
end if;
|
||||
if T2Write = 0 then
|
||||
if TState = "010" and Write = '1' then
|
||||
WR_n <= '0';
|
||||
IORQ_n <= not IORQ;
|
||||
MREQ_n <= IORQ;
|
||||
end if;
|
||||
else
|
||||
if (TState = "001" or (TState = "010" and Wait_n = '0')) and Write = '1' then
|
||||
WR_n <= '0';
|
||||
IORQ_n <= not IORQ;
|
||||
MREQ_n <= IORQ;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
if TState = "010" and Wait_n = '1' then
|
||||
DI_Reg <= DI;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
end;
|
||||
96
vhdl/Z80cpu.vhd
Normal file
96
vhdl/Z80cpu.vhd
Normal file
@ -0,0 +1,96 @@
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
|
||||
--| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
|
||||
--| / __|/ _ \ / __|_ / _ \| | | | | |--
|
||||
--| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
|
||||
--| |___/\___/ \___/___\___/ \___/ | |--
|
||||
--| | http://sowerbutts.com/ |--
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| Wrap the T80 CPU core and produce more easily comprehended signals |--
|
||||
--+-------------------------------------------------------------------------+--
|
||||
|
||||
library IEEE;
|
||||
use IEEE.std_logic_1164.all;
|
||||
use IEEE.numeric_std.all;
|
||||
use work.T80_Pack.all;
|
||||
|
||||
entity Z80cpu is
|
||||
port (
|
||||
-- reset
|
||||
reset : in std_logic;
|
||||
|
||||
-- clocking
|
||||
clk : in std_logic;
|
||||
clk_enable : in std_logic;
|
||||
|
||||
-- indicates when we're in the M1 cycle (start of an instruction)
|
||||
m1_cycle : out std_logic;
|
||||
|
||||
-- memory and I/O interface
|
||||
req_mem : out std_logic; -- memory request?
|
||||
req_io : out std_logic; -- i/o request?
|
||||
req_read : out std_logic; -- read?
|
||||
req_write : out std_logic; -- write?
|
||||
mem_wait : in std_logic; -- memory or i/o can force the CPU to wait
|
||||
address : out std_logic_vector(15 downto 0);
|
||||
data_in : in std_logic_vector(7 downto 0);
|
||||
data_out : out std_logic_vector(7 downto 0);
|
||||
|
||||
-- interrupts
|
||||
interrupt : in std_logic;
|
||||
nmi : in std_logic
|
||||
);
|
||||
end Z80cpu;
|
||||
|
||||
architecture behavioural of Z80cpu is
|
||||
signal RESET_n : std_logic;
|
||||
signal WAIT_n : std_logic;
|
||||
signal INT_n : std_logic;
|
||||
signal NMI_n : std_logic;
|
||||
signal M1_n : std_logic;
|
||||
signal MREQ_n : std_logic;
|
||||
signal IORQ_n : std_logic;
|
||||
signal RFSH_n : std_logic;
|
||||
signal RD_n : std_logic;
|
||||
signal WR_n : std_logic;
|
||||
begin
|
||||
|
||||
RESET_n <= not reset;
|
||||
WAIT_n <= not mem_wait;
|
||||
INT_n <= not interrupt;
|
||||
NMI_n <= not nmi;
|
||||
m1_cycle <= not M1_n;
|
||||
|
||||
req_mem <= (not MREQ_n) and (RFSH_n);
|
||||
req_io <= (not IORQ_n) and (M1_n); -- IORQ is active during M1 when handling interrupts (it's well documented, but I found out the hard way...)
|
||||
req_read <= (not RD_n) and (RFSH_n);
|
||||
req_write <= (not WR_n);
|
||||
|
||||
cpu : entity work.T80se
|
||||
generic map (
|
||||
Mode => 1, -- 0 => Z80, 1 => Fast Z80, 2 => 8080, 3 => GB
|
||||
T2Write => 1, -- 0 => WR_n active in T3, /=0 => WR_n active in T2
|
||||
IOWait => 0 -- 0 => single cycle I/O, 1 => standard I/O cycle
|
||||
)
|
||||
port map (
|
||||
RESET_n => RESET_n,
|
||||
CLK_n => clk,
|
||||
CLKEN => clk_enable,
|
||||
WAIT_n => WAIT_n,
|
||||
INT_n => INT_n,
|
||||
NMI_n => NMI_n,
|
||||
BUSRQ_n => '1',
|
||||
BUSAK_n => open,
|
||||
M1_n => M1_n,
|
||||
MREQ_n => MREQ_n,
|
||||
IORQ_n => IORQ_n,
|
||||
RD_n => RD_n,
|
||||
WR_n => WR_n,
|
||||
RFSH_n => RFSH_n,
|
||||
HALT_n => open,
|
||||
A => address,
|
||||
DI => data_in,
|
||||
DO => data_out
|
||||
);
|
||||
|
||||
end;
|
||||
74
vhdl/clkscale.vhd
Normal file
74
vhdl/clkscale.vhd
Normal file
@ -0,0 +1,74 @@
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
|
||||
--| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
|
||||
--| / __|/ _ \ / __|_ / _ \| | | | | |--
|
||||
--| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
|
||||
--| |___/\___/ \___/___\___/ \___/ | |--
|
||||
--| | http://sowerbutts.com/ |--
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| An attempt to modulate the CPU clock so it can be slowed down without |--
|
||||
--| also modulating the clock to the peripherals (which would break the |--
|
||||
--| UART and DRAM at least). This works but not all the peripherals are |--
|
||||
--| currently compatible (the UART, at least, doesn't handle this well). |--
|
||||
--| Strongly uggest you avoid using this before you fix the peripherals. |--
|
||||
--+-------------------------------------------------------------------------+--
|
||||
|
||||
library IEEE;
|
||||
use IEEE.STD_LOGIC_1164.ALL;
|
||||
use IEEE.NUMERIC_STD.ALL;
|
||||
|
||||
entity clkscale is
|
||||
port ( clk : in std_logic;
|
||||
reset : in std_logic;
|
||||
cpu_address : in std_logic_vector(2 downto 0);
|
||||
data_in : in std_logic_vector(7 downto 0);
|
||||
data_out : out std_logic_vector(7 downto 0);
|
||||
enable : in std_logic;
|
||||
read_notwrite : in std_logic;
|
||||
clk_enable : out std_logic
|
||||
);
|
||||
end clkscale;
|
||||
|
||||
-- a counter which counts up until it reaches a target value.
|
||||
-- when the counter is at the target value the clock is enabled
|
||||
-- for one cycle and the counter is reset. the clock is disabled
|
||||
-- the rest of the time. this means the clock is enabled in the
|
||||
-- proportion 1/(1+r) where r is the register value.
|
||||
|
||||
architecture Behavioral of clkscale is
|
||||
|
||||
signal counter_target : unsigned(7 downto 0) := (others => '0');
|
||||
signal counter_value : unsigned(7 downto 0) := (others => '0');
|
||||
signal output : std_logic;
|
||||
|
||||
begin
|
||||
|
||||
data_out <= std_logic_vector(counter_target);
|
||||
clk_enable <= output;
|
||||
|
||||
clkscale_proc: process(clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
if reset = '1' then
|
||||
counter_target <= to_unsigned(0, 8);
|
||||
counter_value <= to_unsigned(0, 8);
|
||||
output <= '1';
|
||||
else
|
||||
-- reset on target, enable clock for one cycle
|
||||
if counter_value = counter_target then
|
||||
counter_value <= to_unsigned(0, 8);
|
||||
output <= '1';
|
||||
else
|
||||
counter_value <= counter_value + 1;
|
||||
output <= '0';
|
||||
end if;
|
||||
|
||||
-- register write
|
||||
if enable = '1' and read_notwrite = '0' then
|
||||
counter_target <= unsigned(data_in);
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
end Behavioral;
|
||||
|
||||
93
vhdl/fifo.vhd
Normal file
93
vhdl/fifo.vhd
Normal file
@ -0,0 +1,93 @@
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
|
||||
--| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
|
||||
--| / __|/ _ \ / __|_ / _ \| | | | | |--
|
||||
--| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
|
||||
--| |___/\___/ \___/___\___/ \___/ | |--
|
||||
--| | http://sowerbutts.com/ |--
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| FIFO implementation with high water mark. Could be improved; currently |--
|
||||
--| it is impossible to use the last byte in the FIFO (because it cannot |--
|
||||
--| distinguish completely-full from completely-empty) |--
|
||||
--+-------------------------------------------------------------------------+--
|
||||
|
||||
library IEEE;
|
||||
use IEEE.std_logic_1164.all;
|
||||
use IEEE.numeric_std.all;
|
||||
|
||||
entity fifo is
|
||||
generic(
|
||||
depth_log2 : integer := 10; -- 5 gives 32 bytes, implements without a BRAM.
|
||||
hwm_space : integer := 5; -- minimum bytes free in buffer before we assert flow control signals
|
||||
width : integer := 8
|
||||
);
|
||||
port(
|
||||
clk : in std_logic;
|
||||
reset : in std_logic;
|
||||
write_en : in std_logic;
|
||||
write_ready : out std_logic; -- is there space to write?
|
||||
read_en : in std_logic;
|
||||
read_ready : out std_logic; -- is there data waiting to read?
|
||||
data_in : in std_logic_vector(width-1 downto 0);
|
||||
data_out : out std_logic_vector(width-1 downto 0);
|
||||
high_water_mark : out std_logic
|
||||
);
|
||||
end fifo;
|
||||
|
||||
architecture behaviour of fifo is
|
||||
type fifo_entry is array (natural range <>) of std_logic_vector(width-1 downto 0);
|
||||
signal fifo_contents : fifo_entry(0 to (2 ** depth_log2) - 1); -- this is the FIFO buffer memory
|
||||
|
||||
signal read_ptr : unsigned(depth_log2-1 downto 0) := (others => '0');
|
||||
signal write_ptr : unsigned(depth_log2-1 downto 0) := (others => '0');
|
||||
signal full : std_logic;
|
||||
signal empty : std_logic;
|
||||
begin
|
||||
|
||||
is_empty: process(read_ptr, write_ptr)
|
||||
begin
|
||||
if read_ptr = write_ptr then
|
||||
empty <= '1';
|
||||
else
|
||||
empty <= '0';
|
||||
end if;
|
||||
if read_ptr = (write_ptr+1) then
|
||||
full <= '1';
|
||||
else
|
||||
full <= '0';
|
||||
end if;
|
||||
if (write_ptr - read_ptr) >= ((2 ** depth_log2) - 1 - hwm_space) then
|
||||
high_water_mark <= '1';
|
||||
else
|
||||
high_water_mark <= '0';
|
||||
end if;
|
||||
end process;
|
||||
|
||||
fifo_update: process(clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
if reset = '1' then
|
||||
-- reset
|
||||
read_ptr <= to_unsigned(0, depth_log2);
|
||||
write_ptr <= to_unsigned(0, depth_log2);
|
||||
else
|
||||
-- normal operation
|
||||
if write_en = '1' and full = '0' then
|
||||
fifo_contents(to_integer(write_ptr)) <= data_in;
|
||||
write_ptr <= write_ptr + 1;
|
||||
end if;
|
||||
|
||||
if read_en = '1' and empty = '0' then
|
||||
read_ptr <= read_ptr + 1;
|
||||
end if;
|
||||
|
||||
data_out <= fifo_contents(to_integer(read_ptr));
|
||||
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
write_ready <= not full;
|
||||
read_ready <= not empty;
|
||||
|
||||
end;
|
||||
64
vhdl/gpio.vhd
Normal file
64
vhdl/gpio.vhd
Normal file
@ -0,0 +1,64 @@
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
|
||||
--| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
|
||||
--| / __|/ _ \ / __|_ / _ \| | | | | |--
|
||||
--| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
|
||||
--| |___/\___/ \___/___\___/ \___/ | |--
|
||||
--| | http://sowerbutts.com/ |--
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| Simple GPIO interface providing 8 bits each of input and output |--
|
||||
--+-------------------------------------------------------------------------+--
|
||||
|
||||
library IEEE;
|
||||
use IEEE.STD_LOGIC_1164.ALL;
|
||||
use IEEE.NUMERIC_STD.ALL;
|
||||
|
||||
entity gpio is
|
||||
port ( clk : in std_logic;
|
||||
reset : in std_logic;
|
||||
cpu_address : in std_logic_vector(2 downto 0);
|
||||
data_in : in std_logic_vector(7 downto 0);
|
||||
data_out : out std_logic_vector(7 downto 0);
|
||||
enable : in std_logic;
|
||||
read_notwrite : in std_logic;
|
||||
input_pins : in std_logic_vector(7 downto 0);
|
||||
output_pins : out std_logic_vector(7 downto 0)
|
||||
);
|
||||
end gpio;
|
||||
|
||||
architecture Behavioral of gpio is
|
||||
|
||||
signal captured_inputs : std_logic_vector(7 downto 0);
|
||||
signal register_outputs : std_logic_vector(7 downto 0) := (others => '1');
|
||||
|
||||
begin
|
||||
|
||||
with cpu_address select
|
||||
data_out <=
|
||||
captured_inputs when "000",
|
||||
register_outputs when "001",
|
||||
register_outputs when others;
|
||||
|
||||
output_pins <= register_outputs;
|
||||
|
||||
gpio_proc: process(clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
if reset = '1' then
|
||||
captured_inputs <= (others => '0');
|
||||
register_outputs <= (others => '1');
|
||||
else
|
||||
captured_inputs <= input_pins;
|
||||
|
||||
if enable = '1' and read_notwrite = '0' then
|
||||
case cpu_address is
|
||||
when "000" => -- no change
|
||||
when "001" => register_outputs <= data_in;
|
||||
when others => -- no change
|
||||
end case;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
end Behavioral;
|
||||
|
||||
183
vhdl/timer.vhd
Normal file
183
vhdl/timer.vhd
Normal file
@ -0,0 +1,183 @@
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
|
||||
--| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
|
||||
--| / __|/ _ \ / __|_ / _ \| | | | | |--
|
||||
--| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
|
||||
--| |___/\___/ \___/___\___/ \___/ | |--
|
||||
--| | http://sowerbutts.com/ |--
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| A simple timer peripheral for timing intervals and generating periodic |--
|
||||
--| interrupts. |--
|
||||
--+-------------------------------------------------------------------------+--
|
||||
--
|
||||
-- There are two timers; a 1MHz 32-bit counter which always counts up (unless reset to 0)
|
||||
-- and whose value can be transferred atomically to a 32-bit latch, and a 1MHz 24-bit down
|
||||
-- counter which triggers an interrupt and is reset to a programmable value upon reaching
|
||||
-- zero. Writes to the register at base+1 perform timer operations according to the value
|
||||
-- written. The 1MHz is derived by prescaling the system clock.
|
||||
--
|
||||
-- register layout:
|
||||
--
|
||||
-- address read value write operation
|
||||
-- --------- ------------------------------------ -------------------------------------
|
||||
-- base+0 status register set status register
|
||||
-- base+1 (unused) perform operation according to value written
|
||||
-- base+2 (unused) (no operation)
|
||||
-- base+3 (unused) (no operation)
|
||||
-- base+4 muxed register value (low byte) update muxed register value
|
||||
-- base+5 muxed register value update muxed register value
|
||||
-- base+6 muxed register value update muxed register value
|
||||
-- base+7 muxed register value (high byte) update muxed register value
|
||||
--
|
||||
--
|
||||
-- operation values (for writes to base+1):
|
||||
-- 00 -- acknowledge interrupt
|
||||
-- 01 -- reset upcounter value to zero
|
||||
-- 02 -- update latched value from upcounter value
|
||||
-- 03 -- reset downcounter
|
||||
-- 10 -- set register mux select to upcounter current
|
||||
-- 11 -- set register mux select to upcounter latched
|
||||
-- 12 -- set register mux select to downcounter current
|
||||
-- 13 -- set register mux select to downcountre reset
|
||||
--
|
||||
--
|
||||
-- control/status register layout:
|
||||
-- bits 0, 1, -- register mux select (controls which register is visible in registers at base+4 through base+7):
|
||||
-- 0 0 upcounter current value
|
||||
-- 0 1 upcounter latched value
|
||||
-- 1 0 downcounter current value
|
||||
-- 1 1 downcounter reset value
|
||||
-- (bits 2, 3, 4, 5 are currently unused)
|
||||
-- bit 6 -- countdown timer interrupt enable (0=disable, 1=enable)
|
||||
-- bit 7 -- interrupt flag (0=no interrupt, 1=one or interrupts occurred but not yet acknowledged)
|
||||
--
|
||||
|
||||
library IEEE;
|
||||
use IEEE.STD_LOGIC_1164.ALL;
|
||||
use IEEE.NUMERIC_STD.ALL;
|
||||
|
||||
entity timer is
|
||||
generic (
|
||||
clk_frequency : natural := (128 * 1000000)
|
||||
);
|
||||
port ( clk : in std_logic;
|
||||
reset : in std_logic;
|
||||
cpu_address : in std_logic_vector(2 downto 0);
|
||||
data_in : in std_logic_vector(7 downto 0);
|
||||
data_out : out std_logic_vector(7 downto 0);
|
||||
enable : in std_logic;
|
||||
req_read : in std_logic;
|
||||
req_write : in std_logic;
|
||||
interrupt : out std_logic
|
||||
);
|
||||
end timer;
|
||||
|
||||
architecture Behavioral of timer is
|
||||
|
||||
signal upcounter_value : unsigned(31 downto 0) := (others => '0');
|
||||
signal upcounter_latch : unsigned(31 downto 0) := (others => '0');
|
||||
signal downcounter_value : unsigned(31 downto 0) := (others => '0');
|
||||
signal downcounter_start : unsigned(31 downto 0) := (others => '0');
|
||||
|
||||
-- if using frequencies > 128MHz this counter will need to be wider than 7 bits
|
||||
signal counter_prescale : unsigned(6 downto 0) := (others => '0');
|
||||
constant prescale_wrap : unsigned(6 downto 0) := to_unsigned((clk_frequency / 1000000) - 1, 7); -- aim for a 1MHz counter
|
||||
|
||||
signal interrupt_enable : std_logic := '0';
|
||||
signal interrupt_signal : std_logic := '0';
|
||||
signal regmux_select : std_logic_vector(1 downto 0) := "00";
|
||||
|
||||
signal regmux_output : std_logic_vector(31 downto 0);
|
||||
signal regmux_updated : std_logic_vector(31 downto 0);
|
||||
signal status_register_value: std_logic_vector(7 downto 0);
|
||||
|
||||
begin
|
||||
|
||||
interrupt <= (interrupt_signal and interrupt_enable);
|
||||
|
||||
with cpu_address select
|
||||
data_out <=
|
||||
status_register_value when "000",
|
||||
regmux_output(7 downto 0) when "100",
|
||||
regmux_output(15 downto 8) when "101",
|
||||
regmux_output(23 downto 16) when "110",
|
||||
regmux_output(31 downto 24) when "111",
|
||||
status_register_value when others;
|
||||
|
||||
status_register_value <= interrupt_signal & interrupt_enable & "0000" & regmux_select;
|
||||
|
||||
with regmux_select select
|
||||
regmux_output <=
|
||||
std_logic_vector(upcounter_value ) when "00",
|
||||
std_logic_vector(upcounter_latch ) when "01",
|
||||
std_logic_vector(downcounter_value) when "10",
|
||||
std_logic_vector(downcounter_start) when "11",
|
||||
std_logic_vector(downcounter_start) when others;
|
||||
|
||||
with cpu_address(1 downto 0) select
|
||||
regmux_updated <=
|
||||
regmux_output(31 downto 8) & data_in when "00",
|
||||
regmux_output(31 downto 16) & data_in & regmux_output(7 downto 0) when "01",
|
||||
regmux_output(31 downto 24) & data_in & regmux_output(15 downto 0) when "10",
|
||||
data_in & regmux_output(23 downto 0) when "11",
|
||||
data_in & regmux_output(23 downto 0) when others;
|
||||
|
||||
counter_proc: process(clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
if reset = '1' then
|
||||
upcounter_value <= (others => '0');
|
||||
upcounter_latch <= (others => '0');
|
||||
downcounter_value <= (others => '0');
|
||||
downcounter_start <= (others => '0');
|
||||
counter_prescale <= (others => '0');
|
||||
interrupt_enable <= '0';
|
||||
interrupt_signal <= '0';
|
||||
regmux_select <= "00";
|
||||
else
|
||||
-- prescaled counter
|
||||
if counter_prescale = prescale_wrap then
|
||||
counter_prescale <= (others => '0'); -- reset prescale counter
|
||||
upcounter_value <= upcounter_value + 1;
|
||||
if downcounter_value = 0 then
|
||||
downcounter_value <= downcounter_start;
|
||||
interrupt_signal <= '1';
|
||||
else
|
||||
downcounter_value <= downcounter_value - 1;
|
||||
end if;
|
||||
else
|
||||
counter_prescale <= counter_prescale + 1;
|
||||
end if;
|
||||
|
||||
if enable = '1' and req_write = '1' then
|
||||
if cpu_address = "000" then
|
||||
interrupt_signal <= data_in(7);
|
||||
interrupt_enable <= data_in(6);
|
||||
regmux_select <= data_in(1 downto 0);
|
||||
elsif cpu_address = "001" then
|
||||
case data_in is
|
||||
when "00000000" => interrupt_signal <= '0';
|
||||
when "00000001" => upcounter_value <= (others => '0');
|
||||
when "00000010" => upcounter_latch <= upcounter_value;
|
||||
when "00000011" => downcounter_value <= downcounter_start;
|
||||
when "00010000" => regmux_select <= "00";
|
||||
when "00010001" => regmux_select <= "01";
|
||||
when "00010010" => regmux_select <= "10";
|
||||
when "00010011" => regmux_select <= "11";
|
||||
when others =>
|
||||
end case;
|
||||
elsif cpu_address(2) = '1' then
|
||||
case regmux_select is
|
||||
when "00" => upcounter_value <= unsigned(regmux_updated);
|
||||
when "01" => upcounter_latch <= unsigned(regmux_updated);
|
||||
when "10" => downcounter_value <= unsigned(regmux_updated);
|
||||
when "11" => downcounter_start <= unsigned(regmux_updated);
|
||||
when others =>
|
||||
end case;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
end Behavioral;
|
||||
|
||||
563
vhdl/top_level.vhd
Normal file
563
vhdl/top_level.vhd
Normal file
@ -0,0 +1,563 @@
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
|
||||
--| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
|
||||
--| / __|/ _ \ / __|_ / _ \| | | | | |--
|
||||
--| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
|
||||
--| |___/\___/ \___/___\___/ \___/ | |--
|
||||
--| | http://sowerbutts.com/ |--
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| Top level module: connects modules to each other and the outside world |--
|
||||
--+-------------------------------------------------------------------------+--
|
||||
--
|
||||
-- See README.txt for more details
|
||||
--
|
||||
|
||||
library IEEE;
|
||||
use IEEE.STD_LOGIC_1164.ALL;
|
||||
use IEEE.NUMERIC_STD.ALL;
|
||||
library UNISIM;
|
||||
use UNISIM.VComponents.all;
|
||||
|
||||
entity top_level is
|
||||
Port ( sysclk_32m : in std_logic;
|
||||
leds : out std_logic_vector(4 downto 0);
|
||||
reset_button : in std_logic;
|
||||
console_select : in std_logic;
|
||||
|
||||
-- UART0 (to FTDI USB chip, no flow control)
|
||||
serial_rx : in std_logic;
|
||||
serial_tx : out std_logic;
|
||||
|
||||
-- UART0 (to MAX3232 level shifter chip, hardware flow control)
|
||||
uart1_rx : in std_logic;
|
||||
uart1_cts : in std_logic;
|
||||
uart1_tx : out std_logic;
|
||||
uart1_rts : out std_logic;
|
||||
|
||||
-- SPI flash chip
|
||||
flash_spi_cs : out std_logic;
|
||||
flash_spi_clk : out std_logic;
|
||||
flash_spi_mosi : out std_logic;
|
||||
flash_spi_miso : in std_logic;
|
||||
|
||||
-- SD card socket
|
||||
sdcard_spi_cs : out std_logic;
|
||||
sdcard_spi_clk : out std_logic;
|
||||
sdcard_spi_mosi : out std_logic;
|
||||
sdcard_spi_miso : in std_logic;
|
||||
|
||||
-- SDRAM chip
|
||||
SDRAM_CLK : out std_logic;
|
||||
SDRAM_CKE : out std_logic;
|
||||
SDRAM_CS : out std_logic;
|
||||
SDRAM_nRAS : out std_logic;
|
||||
SDRAM_nCAS : out std_logic;
|
||||
SDRAM_nWE : out std_logic;
|
||||
SDRAM_DQM : out std_logic_vector( 1 downto 0);
|
||||
SDRAM_ADDR : out std_logic_vector (12 downto 0);
|
||||
SDRAM_BA : out std_logic_vector( 1 downto 0);
|
||||
SDRAM_DQ : inout std_logic_vector (15 downto 0)
|
||||
);
|
||||
end top_level;
|
||||
|
||||
architecture Behavioral of top_level is
|
||||
constant clk_freq_mhz : natural := 128; -- this is the frequency which the PLL outputs, in MHz.
|
||||
|
||||
-- SDRAM configuration
|
||||
constant sdram_address_width : natural := 22;
|
||||
constant sdram_column_bits : natural := 8;
|
||||
constant cycles_per_refresh : natural := (64000*clk_freq_mhz)/4096-1;
|
||||
|
||||
-- For simulation, we don't need a long init stage. but for real DRAM we need approx 101us.
|
||||
-- The constant below has a different value when interpreted by the synthesis and simulator
|
||||
-- tools in order to achieve the desired timing in each.
|
||||
constant sdram_startup_cycles: natural := 101 * clk_freq_mhz
|
||||
-- pragma translate_off
|
||||
- 10000 -- reduce the value the simulator uses
|
||||
-- pragma translate_on
|
||||
;
|
||||
|
||||
-- signals for clocking
|
||||
signal clk_feedback : std_logic; -- PLL clock feedback
|
||||
signal clk_unbuffered : std_logic; -- unbuffered system clock
|
||||
signal clk : std_logic; -- buffered system clock (all logic should be clocked by this)
|
||||
|
||||
-- console latch
|
||||
signal console_select_clk1 : std_logic;
|
||||
signal console_select_sync : std_logic;
|
||||
signal swap_uart01 : std_logic := '0';
|
||||
|
||||
-- system reset signals
|
||||
signal power_on_reset : std_logic_vector(1 downto 0) := (others => '1');
|
||||
signal system_reset : std_logic;
|
||||
signal reset_button_clk1 : std_logic;
|
||||
signal reset_button_sync : std_logic; -- reset button signal, synchronised to our clock
|
||||
signal reset_request_uart : std_logic; -- reset request signal from FTDI UART (when you send "!~!~!~" to the UART, this line is asserted)
|
||||
|
||||
-- CPU control
|
||||
signal coldboot : std_logic;
|
||||
signal cpu_clk_enable : std_logic;
|
||||
signal cpu_m1_cycle : std_logic;
|
||||
signal cpu_req_mem : std_logic;
|
||||
signal cpu_req_io : std_logic;
|
||||
signal req_mem : std_logic;
|
||||
signal req_io : std_logic;
|
||||
signal req_read : std_logic;
|
||||
signal req_write : std_logic;
|
||||
signal virtual_address : std_logic_vector(15 downto 0);
|
||||
signal physical_address : std_logic_vector(25 downto 0);
|
||||
signal mem_wait : std_logic;
|
||||
signal cpu_wait : std_logic;
|
||||
signal dram_wait : std_logic;
|
||||
signal mmu_wait : std_logic;
|
||||
signal spimaster0_wait : std_logic;
|
||||
signal spimaster1_wait : std_logic;
|
||||
|
||||
-- chip selects
|
||||
signal mmu_cs : std_logic;
|
||||
signal rom_cs : std_logic;
|
||||
signal sram_cs : std_logic;
|
||||
signal dram_cs : std_logic;
|
||||
signal uartA_cs : std_logic;
|
||||
signal uartB_cs : std_logic;
|
||||
signal uart0_cs : std_logic;
|
||||
signal uart1_cs : std_logic;
|
||||
signal timer_cs : std_logic;
|
||||
signal spimaster0_cs : std_logic;
|
||||
signal spimaster1_cs : std_logic;
|
||||
signal clkscale_cs : std_logic;
|
||||
signal gpio_cs : std_logic;
|
||||
|
||||
-- data bus
|
||||
signal cpu_data_in : std_logic_vector(7 downto 0);
|
||||
signal cpu_data_out : std_logic_vector(7 downto 0);
|
||||
signal rom_data_out : std_logic_vector(7 downto 0);
|
||||
signal sram_data_out : std_logic_vector(7 downto 0);
|
||||
signal dram_data_out : std_logic_vector(7 downto 0);
|
||||
signal uart0_data_out : std_logic_vector(7 downto 0);
|
||||
signal uart1_data_out : std_logic_vector(7 downto 0);
|
||||
signal timer_data_out : std_logic_vector(7 downto 0);
|
||||
signal spimaster0_data_out : std_logic_vector(7 downto 0);
|
||||
signal spimaster1_data_out : std_logic_vector(7 downto 0);
|
||||
signal mmu_data_out : std_logic_vector(7 downto 0);
|
||||
signal clkscale_out : std_logic_vector(7 downto 0);
|
||||
signal gpio_data_out : std_logic_vector(7 downto 0);
|
||||
|
||||
-- GPIO
|
||||
signal gpio_input : std_logic_vector(7 downto 0);
|
||||
signal gpio_output : std_logic_vector(7 downto 0);
|
||||
|
||||
-- Interrupts
|
||||
signal cpu_interrupt_in : std_logic;
|
||||
signal timer_interrupt : std_logic;
|
||||
signal uart0_interrupt : std_logic;
|
||||
signal uart1_interrupt : std_logic;
|
||||
|
||||
begin
|
||||
-- Hold CPU reset high for 8 clock cycles on startup,
|
||||
-- and when the user presses their reset button.
|
||||
process(clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
-- Xilinx advises using two flip-flops are used to bring external
|
||||
-- signals which feed control logic into our clock domain.
|
||||
reset_button_clk1 <= reset_button;
|
||||
reset_button_sync <= reset_button_clk1;
|
||||
console_select_clk1 <= console_select;
|
||||
console_select_sync <= console_select_clk1;
|
||||
|
||||
-- reset the system when requested
|
||||
if (power_on_reset(0) = '1' or reset_button_sync = '1' or reset_request_uart = '1') then
|
||||
system_reset <= '1';
|
||||
else
|
||||
system_reset <= '0';
|
||||
end if;
|
||||
|
||||
-- shift 0s into the power_on_reset shift register from the MSB
|
||||
power_on_reset <= '0' & power_on_reset(power_on_reset'length-1 downto 1);
|
||||
|
||||
-- During reset, latch the console select jumper. This is used to
|
||||
-- optionally swap over the UART roles and move the system console to
|
||||
-- the second serial port on the IO board.
|
||||
if system_reset = '1' then
|
||||
swap_uart01 <= console_select_sync;
|
||||
else
|
||||
swap_uart01 <= swap_uart01;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
-- GPIO input signal routing
|
||||
gpio_input <= coldboot & swap_uart01 & "000000";
|
||||
|
||||
-- GPIO output signal routing
|
||||
leds(0) <= gpio_output(0);
|
||||
leds(1) <= gpio_output(1);
|
||||
leds(2) <= gpio_output(2);
|
||||
leds(3) <= gpio_output(3);
|
||||
|
||||
-- User LED (LED1) on Papilio Pro indicates when the CPU is being asked to wait (eg by the SDRAM cache)
|
||||
leds(4) <= cpu_wait;
|
||||
|
||||
-- Interrupt signal for the CPU
|
||||
cpu_interrupt_in <= (timer_interrupt or uart0_interrupt or uart1_interrupt);
|
||||
|
||||
-- Z80 CPU core
|
||||
cpu: entity work.Z80cpu
|
||||
port map (
|
||||
reset => system_reset,
|
||||
clk => clk,
|
||||
clk_enable => cpu_clk_enable,
|
||||
m1_cycle => cpu_m1_cycle,
|
||||
interrupt => cpu_interrupt_in,
|
||||
nmi => '0',
|
||||
req_mem => cpu_req_mem,
|
||||
req_io => cpu_req_io,
|
||||
req_read => req_read,
|
||||
req_write => req_write,
|
||||
mem_wait => cpu_wait,
|
||||
address => virtual_address,
|
||||
data_in => cpu_data_in,
|
||||
data_out => cpu_data_out
|
||||
);
|
||||
|
||||
-- Memory management unit
|
||||
mmu: entity work.MMU
|
||||
port map (
|
||||
reset => system_reset,
|
||||
clk => clk,
|
||||
address_in => virtual_address,
|
||||
address_out => physical_address,
|
||||
cpu_data_in => cpu_data_out,
|
||||
cpu_data_out => mmu_data_out,
|
||||
req_mem_in => cpu_req_mem,
|
||||
req_io_in => cpu_req_io,
|
||||
req_mem_out => req_mem,
|
||||
req_io_out => req_io,
|
||||
req_read => req_read,
|
||||
req_write => req_write,
|
||||
io_cs => mmu_cs,
|
||||
cpu_wait => mmu_wait,
|
||||
access_violated => open -- for now!!
|
||||
);
|
||||
|
||||
-- This process determines which IO or memory device the CPU is addressing
|
||||
-- and asserts the appropriate chip select signals.
|
||||
cs_process: process(req_mem, req_io, physical_address, virtual_address, uartA_cs, uartB_cs, swap_uart01)
|
||||
begin
|
||||
-- memory chip selects: default to unselected
|
||||
rom_cs <= '0';
|
||||
sram_cs <= '0';
|
||||
dram_cs <= '0';
|
||||
|
||||
-- io chip selects: default to unselected
|
||||
uartA_cs <= '0';
|
||||
uartB_cs <= '0';
|
||||
mmu_cs <= '0';
|
||||
timer_cs <= '0';
|
||||
spimaster0_cs <= '0';
|
||||
spimaster1_cs <= '0';
|
||||
clkscale_cs <= '0';
|
||||
gpio_cs <= '0';
|
||||
|
||||
-- memory address decoding
|
||||
-- address space is organised as:
|
||||
-- 0x0 000 000 - 0x0 FFF FFF 16MB DRAM (cached) (mapped to 8MB DRAM twice)
|
||||
-- 0x1 000 000 - 0x1 FFF FFF 16MB DRAM (uncached) (mapped to 8MB DRAM twice)
|
||||
-- 0x2 000 000 - 0x2 000 FFF 4KB monitor ROM (FPGA block RAM)
|
||||
-- 0x2 001 000 - 0x2 001 FFF 4KB SRAM (FPGA block RAM)
|
||||
-- 0x2 002 000 - 0x3 FFF FFF unused space for future expansion
|
||||
if physical_address(25) = '0' then
|
||||
-- bottom 32MB: DRAM handles this
|
||||
dram_cs <= req_mem;
|
||||
else
|
||||
-- top 32MB: other memory devices
|
||||
case physical_address(24 downto 12) is
|
||||
when "0000000000000" => rom_cs <= req_mem;
|
||||
when "0000000000001" => sram_cs <= req_mem;
|
||||
when others => -- undecoded memory space
|
||||
end case;
|
||||
end if;
|
||||
|
||||
-- IO address decoding
|
||||
case virtual_address(7 downto 3) is
|
||||
when "00000" => uartA_cs <= req_io; -- 00 ... 07
|
||||
when "00010" => timer_cs <= req_io; -- 10 ... 17
|
||||
when "00011" => spimaster0_cs <= req_io; -- 18 ... 1F
|
||||
when "00100" => gpio_cs <= req_io; -- 20 ... 27
|
||||
when "00101" => uartB_cs <= req_io; -- 28 ... 2F
|
||||
when "00110" => spimaster1_cs <= req_io; -- 30 ... 37
|
||||
-- unused ports
|
||||
when "11110" => clkscale_cs <= req_io; -- F0 ... F7
|
||||
when "11111" => mmu_cs <= req_io; -- F8 ... FF
|
||||
when others =>
|
||||
end case;
|
||||
|
||||
-- send the UART chip select to the appropriate UART depending
|
||||
-- on whether they have been swapped over or not.
|
||||
if swap_uart01 = '0' then
|
||||
uart0_cs <= uartB_cs;
|
||||
uart1_cs <= uartA_cs;
|
||||
else
|
||||
uart0_cs <= uartA_cs;
|
||||
uart1_cs <= uartB_cs;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
-- the selected memory device can request the CPU to wait
|
||||
mem_wait <=
|
||||
dram_wait when dram_cs='1' else
|
||||
spimaster0_wait when spimaster0_cs='1' else
|
||||
spimaster1_wait when spimaster1_cs='1' else
|
||||
'0';
|
||||
|
||||
-- the MMU can, at any time, request the CPU wait (this is used when
|
||||
-- translating IO to memory requests, to implement a wait state for
|
||||
-- the "17th page")
|
||||
cpu_wait <= (mem_wait or mmu_wait);
|
||||
|
||||
-- input mux for CPU data bus
|
||||
cpu_data_in <=
|
||||
rom_data_out when rom_cs='1' else
|
||||
dram_data_out when dram_cs='1' else
|
||||
sram_data_out when sram_cs='1' else
|
||||
uart0_data_out when uart0_cs='1' else
|
||||
uart1_data_out when uart1_cs='1' else
|
||||
timer_data_out when timer_cs='1' else
|
||||
mmu_data_out when mmu_cs='1' else
|
||||
spimaster0_data_out when spimaster0_cs='1' else
|
||||
spimaster1_data_out when spimaster1_cs='1' else
|
||||
clkscale_out when clkscale_cs='1' else
|
||||
gpio_data_out when gpio_cs='1' else
|
||||
rom_data_out; -- default case
|
||||
|
||||
dram: entity work.DRAM
|
||||
generic map(
|
||||
sdram_address_width => sdram_address_width,
|
||||
sdram_column_bits => sdram_column_bits,
|
||||
sdram_startup_cycles=> sdram_startup_cycles,
|
||||
cycles_per_refresh => cycles_per_refresh
|
||||
)
|
||||
port map(
|
||||
clk => clk,
|
||||
reset => '0', -- important to note that we DO NOT reset the SDRAM controller on reset (it would stop refreshing, which would be bad)
|
||||
|
||||
-- interface to synthetic CPU
|
||||
cs => dram_cs,
|
||||
req_read => req_read,
|
||||
req_write => req_write,
|
||||
mem_address => physical_address(24 downto 0),
|
||||
mem_wait => dram_wait,
|
||||
data_in => cpu_data_out,
|
||||
data_out => dram_data_out,
|
||||
coldboot => coldboot,
|
||||
|
||||
-- interface to hardware SDRAM chip
|
||||
SDRAM_CLK => SDRAM_CLK,
|
||||
SDRAM_CKE => SDRAM_CKE,
|
||||
SDRAM_CS => SDRAM_CS,
|
||||
SDRAM_nRAS => SDRAM_nRAS,
|
||||
SDRAM_nCAS => SDRAM_nCAS,
|
||||
SDRAM_nWE => SDRAM_nWE,
|
||||
SDRAM_DQM => SDRAM_DQM,
|
||||
SDRAM_BA => SDRAM_BA,
|
||||
SDRAM_ADDR => SDRAM_ADDR,
|
||||
SDRAM_DQ => SDRAM_DQ
|
||||
);
|
||||
|
||||
-- 4KB system ROM implemented in block RAM
|
||||
rom: entity work.MonZ80
|
||||
port map(
|
||||
clk => clk,
|
||||
A => physical_address(11 downto 0),
|
||||
D => rom_data_out
|
||||
);
|
||||
|
||||
-- 4KB SRAM memory implemented in block RAM
|
||||
sram: entity work.SSRAM
|
||||
generic map(
|
||||
AddrWidth => 12
|
||||
)
|
||||
port map(
|
||||
clk => clk,
|
||||
ce => sram_cs,
|
||||
we => req_write,
|
||||
A => physical_address(11 downto 0),
|
||||
DIn => cpu_data_out,
|
||||
DOut => sram_data_out
|
||||
);
|
||||
|
||||
-- UART connected to FTDI USB UART
|
||||
uart0: entity work.uart_interface
|
||||
generic map ( watch_for_reset => 1, clk_frequency => (clk_freq_mhz * 1000000) )
|
||||
port map(
|
||||
clk => clk,
|
||||
reset => system_reset,
|
||||
reset_out => reset_request_uart, -- result of watching for reset sequence on the input
|
||||
serial_in => serial_rx,
|
||||
serial_out => serial_tx,
|
||||
serial_rts => open,
|
||||
serial_cts => '0',
|
||||
cpu_address => virtual_address(2 downto 0),
|
||||
cpu_data_in => cpu_data_out,
|
||||
cpu_data_out => uart0_data_out,
|
||||
enable => uart0_cs,
|
||||
interrupt => uart0_interrupt,
|
||||
req_read => req_read,
|
||||
req_write => req_write
|
||||
);
|
||||
|
||||
-- UART connected to MAX3232 on optional IO board
|
||||
uart1: entity work.uart_interface
|
||||
generic map ( flow_control => 1, clk_frequency => (clk_freq_mhz * 1000000) )
|
||||
port map(
|
||||
clk => clk,
|
||||
reset => system_reset,
|
||||
serial_in => uart1_rx,
|
||||
serial_out => uart1_tx,
|
||||
serial_rts => uart1_rts,
|
||||
serial_cts => uart1_cts,
|
||||
cpu_address => virtual_address(2 downto 0),
|
||||
cpu_data_in => cpu_data_out,
|
||||
cpu_data_out => uart1_data_out,
|
||||
enable => uart1_cs,
|
||||
interrupt => uart1_interrupt,
|
||||
req_read => req_read,
|
||||
req_write => req_write
|
||||
);
|
||||
|
||||
-- Timer device (internally scales the clock to 1MHz)
|
||||
timer: entity work.timer
|
||||
generic map ( clk_frequency => (clk_freq_mhz * 1000000) )
|
||||
port map(
|
||||
clk => clk,
|
||||
reset => system_reset,
|
||||
cpu_address => virtual_address(2 downto 0),
|
||||
data_in => cpu_data_out,
|
||||
data_out => timer_data_out,
|
||||
enable => timer_cs,
|
||||
req_read => req_read,
|
||||
req_write => req_write,
|
||||
interrupt => timer_interrupt
|
||||
);
|
||||
|
||||
-- SPI master device connected to Papilio Pro 8MB flash ROM
|
||||
spimaster0: entity work.spimaster
|
||||
port map(
|
||||
clk => clk,
|
||||
reset => system_reset,
|
||||
cpu_address => virtual_address(2 downto 0),
|
||||
cpu_wait => spimaster0_wait,
|
||||
data_in => cpu_data_out,
|
||||
data_out => spimaster0_data_out,
|
||||
enable => spimaster0_cs,
|
||||
req_read => req_read,
|
||||
req_write => req_write,
|
||||
slave_cs => flash_spi_cs,
|
||||
slave_clk => flash_spi_clk,
|
||||
slave_mosi => flash_spi_mosi,
|
||||
slave_miso => flash_spi_miso
|
||||
);
|
||||
|
||||
-- SPI master device connected to SD card socket on the IO board
|
||||
spimaster1: entity work.spimaster
|
||||
port map(
|
||||
clk => clk,
|
||||
reset => system_reset,
|
||||
cpu_address => virtual_address(2 downto 0),
|
||||
cpu_wait => spimaster1_wait,
|
||||
data_in => cpu_data_out,
|
||||
data_out => spimaster1_data_out,
|
||||
enable => spimaster1_cs,
|
||||
req_read => req_read,
|
||||
req_write => req_write,
|
||||
slave_cs => sdcard_spi_cs,
|
||||
slave_clk => sdcard_spi_clk,
|
||||
slave_mosi => sdcard_spi_mosi,
|
||||
slave_miso => sdcard_spi_miso
|
||||
);
|
||||
|
||||
-- GPIO to FPGA pins and/or internal signals
|
||||
gpio: entity work.gpio
|
||||
port map(
|
||||
clk => clk,
|
||||
reset => system_reset,
|
||||
cpu_address => virtual_address(2 downto 0),
|
||||
data_in => cpu_data_out,
|
||||
data_out => gpio_data_out,
|
||||
enable => gpio_cs,
|
||||
read_notwrite => req_read,
|
||||
input_pins => gpio_input,
|
||||
output_pins => gpio_output
|
||||
);
|
||||
|
||||
-- An attempt to allow the CPU clock to be scaled back to run
|
||||
-- at slower speeds without affecting the clock signal sent to
|
||||
-- IO devices. Basically this was an attempt to make CP/M games
|
||||
-- playable :) Very limited success. Might be simpler to remove
|
||||
-- this entirely.
|
||||
clkscale: entity work.clkscale
|
||||
port map (
|
||||
clk => clk,
|
||||
reset => system_reset,
|
||||
cpu_address => virtual_address(2 downto 0),
|
||||
data_in => cpu_data_out,
|
||||
data_out => clkscale_out,
|
||||
enable => clkscale_cs,
|
||||
read_notwrite => req_read,
|
||||
clk_enable => cpu_clk_enable
|
||||
);
|
||||
|
||||
-- PLL scales 32MHz Papilio Pro oscillator frequency to 128MHz
|
||||
-- clock for our logic.
|
||||
clock_pll: PLL_BASE
|
||||
generic map (
|
||||
BANDWIDTH => "OPTIMIZED", -- "HIGH", "LOW" or "OPTIMIZED"
|
||||
CLKFBOUT_MULT => 16, -- Multiply value for all CLKOUT clock outputs (1-64)
|
||||
CLKFBOUT_PHASE => 0.0, -- Phase offset in degrees of the clock feedback output (0.0-360.0).
|
||||
CLKIN_PERIOD => 31.25, -- Input clock period in ns to ps resolution (i.e. 33.333 is 30 MHz).
|
||||
-- CLKOUT0_DIVIDE - CLKOUT5_DIVIDE: Divide amount for CLKOUT# clock output (1-128)
|
||||
CLKOUT0_DIVIDE => 4, -- 32MHz * 16 / 4 = 128MHz. Adjust clk_freq_mhz constant (above) if you change this.
|
||||
CLKOUT1_DIVIDE => 1,
|
||||
CLKOUT2_DIVIDE => 1,
|
||||
CLKOUT3_DIVIDE => 1,
|
||||
CLKOUT4_DIVIDE => 1,
|
||||
CLKOUT5_DIVIDE => 1,
|
||||
-- CLKOUT0_DUTY_CYCLE - CLKOUT5_DUTY_CYCLE: Duty cycle for CLKOUT# clock output (0.01-0.99).
|
||||
CLKOUT0_DUTY_CYCLE => 0.5, CLKOUT1_DUTY_CYCLE => 0.5,
|
||||
CLKOUT2_DUTY_CYCLE => 0.5, CLKOUT3_DUTY_CYCLE => 0.5,
|
||||
CLKOUT4_DUTY_CYCLE => 0.5, CLKOUT5_DUTY_CYCLE => 0.5,
|
||||
-- CLKOUT0_PHASE - CLKOUT5_PHASE: Output phase relationship for CLKOUT# clock output (-360.0-360.0).
|
||||
CLKOUT0_PHASE => 0.0, CLKOUT1_PHASE => 0.0, -- Capture clock
|
||||
CLKOUT2_PHASE => 0.0, CLKOUT3_PHASE => 0.0,
|
||||
CLKOUT4_PHASE => 0.0, CLKOUT5_PHASE => 0.0,
|
||||
|
||||
CLK_FEEDBACK => "CLKFBOUT", -- Clock source to drive CLKFBIN ("CLKFBOUT" or "CLKOUT0")
|
||||
COMPENSATION => "SYSTEM_SYNCHRONOUS", -- "SYSTEM_SYNCHRONOUS", "SOURCE_SYNCHRONOUS", "EXTERNAL"
|
||||
DIVCLK_DIVIDE => 1, -- Division value for all output clocks (1-52)
|
||||
REF_JITTER => 0.1, -- Reference Clock Jitter in UI (0.000-0.999).
|
||||
RESET_ON_LOSS_OF_LOCK => FALSE -- Must be set to FALSE
|
||||
)
|
||||
port map(
|
||||
CLKIN => sysclk_32m, -- 1-bit input: Clock input
|
||||
CLKFBOUT => clk_feedback, -- 1-bit output: PLL_BASE feedback output
|
||||
CLKFBIN => clk_feedback, -- 1-bit input: Feedback clock input
|
||||
-- CLKOUT0 - CLKOUT5: 1-bit (each) output: Clock outputs
|
||||
CLKOUT0 => clk_unbuffered, -- 64MHz clock output
|
||||
CLKOUT1 => open,
|
||||
CLKOUT2 => open,
|
||||
CLKOUT3 => open,
|
||||
CLKOUT4 => open,
|
||||
CLKOUT5 => open,
|
||||
LOCKED => open, -- 1-bit output: PLL_BASE lock status output
|
||||
RST => '0' -- 1-bit input: Reset input
|
||||
);
|
||||
|
||||
-- Buffering of clocks
|
||||
BUFG_clk: BUFG
|
||||
port map(
|
||||
O => clk,
|
||||
I => clk_unbuffered
|
||||
);
|
||||
|
||||
end Behavioral;
|
||||
193
vhdl/uart.vhd
Normal file
193
vhdl/uart.vhd
Normal file
@ -0,0 +1,193 @@
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
|
||||
--| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
|
||||
--| / __|/ _ \ / __|_ / _ \| | | | | |--
|
||||
--| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
|
||||
--| |___/\___/ \___/___\___/ \___/ | |--
|
||||
--| | http://sowerbutts.com/ |--
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| UART implementation |--
|
||||
--+-------------------------------------------------------------------------+--
|
||||
|
||||
library IEEE;
|
||||
use IEEE.STD_LOGIC_1164.ALL;
|
||||
use IEEE.NUMERIC_STD.ALL;
|
||||
|
||||
entity uart is
|
||||
generic (
|
||||
clk_frequency : natural := (128 * 1000000)
|
||||
);
|
||||
port ( clk : in std_logic;
|
||||
serial_out : out std_logic;
|
||||
serial_in : in std_logic;
|
||||
data_in : in std_logic_vector(7 downto 0);
|
||||
data_in_load : in std_logic;
|
||||
data_out : out std_logic_vector(7 downto 0);
|
||||
data_out_ready : out std_logic;
|
||||
bad_bit : out std_logic;
|
||||
transmitter_busy : out std_logic;
|
||||
can_transmit : in std_logic
|
||||
);
|
||||
end uart;
|
||||
|
||||
architecture Behavioral of uart is
|
||||
|
||||
-- tested at 1,000,000bps with 48MHz clock. Works (apparently).
|
||||
constant rx_sample_interval : unsigned(13 downto 0) := to_unsigned(clk_frequency / (115200 * 16) - 1, 14); -- clock speed / (baud x 16) - 1 ; eg 32MHz / (9600 * 16) - 1 = 207
|
||||
constant bit_duration : unsigned(13 downto 0) := to_unsigned(clk_frequency / (115200 * 1) - 1, 14); -- clock speed / baud - 1 ; eg 32MHz / 9600 - 1 = 3332
|
||||
|
||||
signal tx_counter : unsigned(13 downto 0) := to_unsigned(0, 14);
|
||||
signal tx_shift_reg : std_logic_vector(8 downto 0) := "111111111";
|
||||
signal tx_bits_left : unsigned(3 downto 0) := to_unsigned(0, 4);
|
||||
signal tx_busy : std_logic;
|
||||
|
||||
signal rx_counter : unsigned(13 downto 0) := to_unsigned(0, 14);
|
||||
signal rx_shift_reg : std_logic_vector(8 downto 0) := "000000000";
|
||||
signal rx_bits_got : unsigned(3 downto 0) := to_unsigned(0, 4);
|
||||
signal rx_state : unsigned(7 downto 0) := (others => '0'); -- 10 bits x 16 samples each = at least 160 states.
|
||||
signal rx_out_ready : std_logic := '0';
|
||||
signal data_out_buf : std_logic_vector(7 downto 0) := "00000000";
|
||||
|
||||
signal rx_clkin1 : std_logic := '0';
|
||||
signal rx_clkin2 : std_logic := '0';
|
||||
signal rx_sample1 : std_logic := '0';
|
||||
signal rx_sample2 : std_logic := '0';
|
||||
signal rx_sample3 : std_logic := '0';
|
||||
signal rx_sample_majority : std_logic;
|
||||
|
||||
signal rx_badbit : std_logic := '0';
|
||||
|
||||
begin
|
||||
|
||||
-- -- receiver -- --
|
||||
--
|
||||
-- Incoming data is oversampled 16 times. We check three samples in the
|
||||
-- middle of each bit and take a simple majority. There is provision for
|
||||
-- rejecting noise where a start bit should have been. We compensate for
|
||||
-- small amounts of clock drift by potentially cutting a stop bit short.
|
||||
--
|
||||
-- This is not dissimilar to how the AVR USART receiver works.
|
||||
--
|
||||
|
||||
data_out_ready <= rx_out_ready;
|
||||
bad_bit <= rx_badbit;
|
||||
data_out <= data_out_buf;
|
||||
|
||||
rx_sample_majority <= (rx_sample1 and rx_sample2) or (rx_sample1 and rx_sample3) or (rx_sample2 and rx_sample3); -- simple majority wins
|
||||
|
||||
receiver: process(clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
-- Bring serial_in into our clock domain
|
||||
rx_clkin1 <= serial_in;
|
||||
rx_clkin2 <= rx_clkin1; -- rx_clkin2 should now be safe to use.
|
||||
|
||||
-- We latch the incoming serial data at full clock speed (NOT divided down)
|
||||
rx_sample1 <= rx_clkin2;
|
||||
rx_out_ready <= '0';
|
||||
|
||||
-- bad bit
|
||||
rx_badbit <= rx_badbit;
|
||||
|
||||
-- clock divider
|
||||
rx_counter <= rx_counter + 1;
|
||||
if rx_counter = rx_sample_interval then
|
||||
rx_counter <= (others => '0');
|
||||
|
||||
if rx_state = "00000000" then
|
||||
-- line is in the idle state, we're waiting for a start bit!
|
||||
-- the anticipation is killing me.
|
||||
if rx_sample1 = '0' then
|
||||
rx_state <= "00000001"; -- and we're off!
|
||||
rx_counter <= (others => '0');
|
||||
end if;
|
||||
elsif rx_state = "10011010" then
|
||||
-- wait for the line to be idle. we don't leave this state until the serial line
|
||||
-- goes high (it should be already, because we should be in mid stop bit).
|
||||
if rx_sample1 = '1' then
|
||||
rx_state <= "00000000";
|
||||
end if;
|
||||
else
|
||||
-- we're in the normal bit reception pattern
|
||||
rx_state <= rx_state + 1;
|
||||
|
||||
-- rx_sample1 contains the incoming serial data, latched
|
||||
rx_sample3 <= rx_sample2;
|
||||
rx_sample2 <= rx_sample1;
|
||||
|
||||
-- when we have the three middle samples, update the shift register
|
||||
if rx_state(3 downto 0) = "1001" then
|
||||
rx_shift_reg <= rx_sample_majority & rx_shift_reg(rx_shift_reg'length-1 downto 1);
|
||||
-- false start bit noise rejection: if we read the start bit as a logical 1, start over again.
|
||||
if (rx_state(7 downto 4) = "0000") and (rx_sample_majority = '1') then
|
||||
rx_badbit <= '1';
|
||||
rx_state <= "00000000";
|
||||
end if;
|
||||
-- check stop bit framing and alert CPU if valid byte received
|
||||
if (rx_state(7 downto 4) = "1001") then
|
||||
if (rx_sample_majority = '1') then
|
||||
data_out_buf <= rx_shift_reg(8 downto 1);
|
||||
rx_out_ready <= '1';
|
||||
rx_badbit <= '0';
|
||||
else
|
||||
rx_badbit <= '1';
|
||||
end if;
|
||||
-- if line is high we can skip waiting for the line to go idle which buys us a little more tolerance for clock drift.
|
||||
if rx_sample1 = '1' then
|
||||
rx_state <= "00000000";
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
|
||||
-- -- transmitter -- --
|
||||
--
|
||||
-- just clock out the bits, damn it.
|
||||
--
|
||||
|
||||
serial_out <= tx_shift_reg(0); -- we always output the bottom bit of the shift register.
|
||||
transmitter_busy <= tx_busy; -- or data_in_load;
|
||||
|
||||
transmitter: process(clk)
|
||||
begin
|
||||
|
||||
if rising_edge(clk) then
|
||||
tx_busy <= '1';
|
||||
|
||||
if tx_bits_left = 0 then
|
||||
-- idle
|
||||
if data_in_load = '1' then
|
||||
tx_shift_reg <= data_in & '0'; -- data bits, start bit
|
||||
tx_bits_left <= to_unsigned(10, 4); -- total ten bits to transmit including stop bit
|
||||
tx_counter <= (others => '0'); -- reset counter
|
||||
else
|
||||
if can_transmit = '0' then
|
||||
tx_busy <= '1';
|
||||
else
|
||||
tx_busy <= '0';
|
||||
end if;
|
||||
end if;
|
||||
else
|
||||
-- busy
|
||||
if (tx_counter = 0) and (tx_bits_left = 10) and (can_transmit = '0') then
|
||||
-- do nothing, we're waiting for our peer to indicate that we can transmit
|
||||
tx_counter <= (others => '0');
|
||||
else
|
||||
tx_counter <= tx_counter + 1;
|
||||
if tx_counter = bit_duration then
|
||||
-- shift out the next bit
|
||||
tx_shift_reg <= '1' & tx_shift_reg(8 downto 1); -- stop bit and line idle state are both 1 so shift that in the top
|
||||
tx_counter <= (others => '0'); -- reset counter
|
||||
tx_bits_left <= tx_bits_left - 1;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
end Behavioral;
|
||||
|
||||
216
vhdl/uart_interface.vhd
Normal file
216
vhdl/uart_interface.vhd
Normal file
@ -0,0 +1,216 @@
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
|
||||
--| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
|
||||
--| / __|/ _ \ / __|_ / _ \| | | | | |--
|
||||
--| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
|
||||
--| |___/\___/ \___/___\___/ \___/ | |--
|
||||
--| | http://sowerbutts.com/ |--
|
||||
--+-----------------------------------+-------------------------------------+--
|
||||
--| Combine the UART with a receive FIFO and provide an interface to the |--
|
||||
--| microprocessor. |--
|
||||
--+-------------------------------------------------------------------------+--
|
||||
|
||||
library IEEE;
|
||||
use IEEE.STD_LOGIC_1164.ALL;
|
||||
use IEEE.NUMERIC_STD.ALL;
|
||||
|
||||
entity uart_interface is
|
||||
Generic ( watch_for_reset : integer := 0;
|
||||
clk_frequency : natural := (128*1000000);
|
||||
flow_control : integer := 0) ;
|
||||
Port ( clk : in std_logic;
|
||||
reset : in std_logic;
|
||||
-- rs232
|
||||
serial_in : in std_logic;
|
||||
serial_out : out std_logic;
|
||||
-- flow control (optional)
|
||||
serial_cts : in std_logic;
|
||||
serial_rts : out std_logic;
|
||||
-- memory interface
|
||||
cpu_address : in std_logic_vector (2 downto 0);
|
||||
cpu_data_in : in std_logic_vector (7 downto 0);
|
||||
cpu_data_out : out std_logic_vector (7 downto 0);
|
||||
reset_out : out std_logic;
|
||||
enable : in std_logic;
|
||||
interrupt : out std_logic;
|
||||
req_read : in std_logic;
|
||||
req_write : in std_logic);
|
||||
end uart_interface;
|
||||
|
||||
architecture Behavioral of uart_interface is
|
||||
signal uart_data_in : std_logic_vector(7 downto 0);
|
||||
signal uart_data_out : std_logic_vector(7 downto 0);
|
||||
signal fifo_data_out : std_logic_vector(7 downto 0);
|
||||
signal fifo_data_ready : std_logic;
|
||||
signal uart_rx_ready : std_logic;
|
||||
signal uart_tx_busy : std_logic;
|
||||
signal uart_badbit : std_logic;
|
||||
signal uart_data_load : std_logic;
|
||||
signal fifo_data_ack : std_logic;
|
||||
signal uart_status_register : std_logic_vector(7 downto 0);
|
||||
signal rx_interrupt_enable : std_logic := '0';
|
||||
signal tx_interrupt_enable : std_logic := '0';
|
||||
signal rx_interrupt_signal : std_logic := '0';
|
||||
signal tx_interrupt_signal : std_logic := '0';
|
||||
signal uart_tx_was_busy : std_logic := '0';
|
||||
signal fifo_data_was_ready : std_logic := '0';
|
||||
signal cts_clk1 : std_logic;
|
||||
signal cts_clk2 : std_logic;
|
||||
signal fifo_nearly_full : std_logic;
|
||||
signal can_transmit : std_logic;
|
||||
|
||||
type reset_seq_state is (
|
||||
st_idle,
|
||||
st_seen1,
|
||||
st_seen2,
|
||||
st_seen3,
|
||||
st_seen4,
|
||||
st_seen5
|
||||
);
|
||||
signal reset_seq : reset_seq_state := st_idle;
|
||||
signal reset_saw_byte0 : std_logic;
|
||||
signal reset_saw_byte1 : std_logic;
|
||||
begin
|
||||
|
||||
-- this whole module could really do with a bit of a rethink.
|
||||
with cpu_address select
|
||||
cpu_data_out <=
|
||||
uart_status_register when "000",
|
||||
fifo_data_out when others;
|
||||
|
||||
uart_data_in <= cpu_data_in;
|
||||
|
||||
uart_status_register <= fifo_data_ready & uart_tx_busy & '0' & uart_badbit & rx_interrupt_enable & tx_interrupt_enable & rx_interrupt_signal & tx_interrupt_signal;
|
||||
|
||||
interrupt <= (rx_interrupt_signal and rx_interrupt_enable) or (tx_interrupt_signal and tx_interrupt_enable);
|
||||
|
||||
-- this decodes cpu_address="001" in a rather longwinded way.
|
||||
uart_data_load <= cpu_address(0) and (not cpu_address(1)) and (not cpu_address(2)) and enable and req_write;
|
||||
fifo_data_ack <= cpu_address(0) and (not cpu_address(1)) and (not cpu_address(2)) and enable and req_read;
|
||||
|
||||
-- optional hardware flow control
|
||||
process(fifo_nearly_full, cts_clk2)
|
||||
begin
|
||||
if flow_control = 1 then
|
||||
serial_rts <= fifo_nearly_full;
|
||||
can_transmit <= (not cts_clk2);
|
||||
else
|
||||
serial_rts <= '0';
|
||||
can_transmit <= '1';
|
||||
end if;
|
||||
end process;
|
||||
|
||||
-- optional reset on data sequence
|
||||
process(uart_data_out)
|
||||
begin
|
||||
if watch_for_reset = 1 then
|
||||
if uart_data_out = "00100001" then
|
||||
reset_saw_byte0 <= '1';
|
||||
reset_saw_byte1 <= '0';
|
||||
elsif uart_data_out = "01111110" then
|
||||
reset_saw_byte0 <= '0';
|
||||
reset_saw_byte1 <= '1';
|
||||
else
|
||||
reset_saw_byte0 <= '0';
|
||||
reset_saw_byte1 <= '0';
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
|
||||
process(clk)
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
if reset = '1' then
|
||||
fifo_data_was_ready <= '0';
|
||||
rx_interrupt_enable <= '0';
|
||||
rx_interrupt_signal <= '0';
|
||||
tx_interrupt_enable <= '0';
|
||||
tx_interrupt_signal <= '0';
|
||||
uart_tx_was_busy <= '0';
|
||||
reset_out <= '0';
|
||||
reset_seq <= st_idle;
|
||||
cts_clk1 <= '0';
|
||||
cts_clk2 <= '0';
|
||||
else
|
||||
tx_interrupt_signal <= tx_interrupt_signal;
|
||||
rx_interrupt_signal <= rx_interrupt_signal;
|
||||
|
||||
-- bring CTS into our clock domain
|
||||
if flow_control = 1 then
|
||||
cts_clk1 <= serial_cts;
|
||||
cts_clk2 <= cts_clk1;
|
||||
end if;
|
||||
|
||||
-- handle writes to the status register
|
||||
if enable = '1' and req_write = '1' and cpu_address = "000" then
|
||||
rx_interrupt_enable <= cpu_data_in(3);
|
||||
tx_interrupt_enable <= cpu_data_in(2);
|
||||
rx_interrupt_signal <= cpu_data_in(1);
|
||||
tx_interrupt_signal <= cpu_data_in(0);
|
||||
end if;
|
||||
|
||||
uart_tx_was_busy <= uart_tx_busy;
|
||||
fifo_data_was_ready <= fifo_data_ready;
|
||||
|
||||
if uart_tx_was_busy = '1' and uart_tx_busy = '0' then
|
||||
tx_interrupt_signal <= '1';
|
||||
end if;
|
||||
|
||||
if fifo_data_ready = '1' and fifo_data_was_ready = '0' then
|
||||
rx_interrupt_signal <= '1';
|
||||
end if;
|
||||
|
||||
if watch_for_reset = 1 then
|
||||
if uart_rx_ready = '1' then
|
||||
reset_seq <= st_idle; -- end up here unless we match the conditions below
|
||||
reset_out <= '0';
|
||||
case reset_seq is
|
||||
when st_idle => if reset_saw_byte0 = '1' then reset_seq <= st_seen1; end if;
|
||||
when st_seen1 => if reset_saw_byte1 = '1' then reset_seq <= st_seen2; end if;
|
||||
when st_seen2 => if reset_saw_byte0 = '1' then reset_seq <= st_seen3; end if;
|
||||
when st_seen3 => if reset_saw_byte1 = '1' then reset_seq <= st_seen4; end if;
|
||||
when st_seen4 => if reset_saw_byte0 = '1' then reset_seq <= st_seen5; end if;
|
||||
when st_seen5 => if reset_saw_byte1 = '1' then reset_out <= '1'; end if;
|
||||
when others =>
|
||||
end case;
|
||||
else
|
||||
reset_seq <= reset_seq;
|
||||
reset_out <= '0';
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
fifo_instance: entity work.fifo
|
||||
PORT MAP(
|
||||
clk => clk,
|
||||
reset => reset,
|
||||
data_in => uart_data_out,
|
||||
data_out => fifo_data_out,
|
||||
read_ready => fifo_data_ready,
|
||||
read_en => fifo_data_ack,
|
||||
write_ready => open,
|
||||
write_en => uart_rx_ready,
|
||||
high_water_mark => fifo_nearly_full
|
||||
);
|
||||
|
||||
uart_instance: entity work.uart
|
||||
GENERIC MAP(
|
||||
clk_frequency => clk_frequency
|
||||
)
|
||||
PORT MAP(
|
||||
clk => clk,
|
||||
serial_out => serial_out,
|
||||
serial_in => serial_in,
|
||||
data_in => uart_data_in,
|
||||
data_in_load => uart_data_load,
|
||||
data_out => uart_data_out,
|
||||
data_out_ready => uart_rx_ready,
|
||||
bad_bit => uart_badbit,
|
||||
transmitter_busy => uart_tx_busy,
|
||||
can_transmit => can_transmit
|
||||
);
|
||||
|
||||
end Behavioral;
|
||||
Loading…
x
Reference in New Issue
Block a user