Initial commit.

This commit is contained in:
Sergio L. Pascual 2014-09-06 02:01:39 +02:00 committed by Sergio Lopez
commit 8da567e4b6
19 changed files with 6865 additions and 0 deletions

395
vhdl/DRAM.vhd Normal file
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

371
vhdl/T80_ALU.vhd Normal file
View 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

File diff suppressed because it is too large Load Diff

220
vhdl/T80_Pack.vhd Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;