repo2/atari_chips/pokeyv2/m9k_grouped.vhdl @ 1532
| 1527 | markw | ---------------------------------------------------------------------------
|
|
-- (c) 2026 mark watson
|
|||
-- I am happy for anyone to use this for non-commercial use.
|
|||
-- If my vhdl files are used commercially or otherwise sold,
|
|||
-- please contact me for explicit permission at scrameta (gmail).
|
|||
-- This applies for source and binary form and derived works.
|
|||
--
|
|||
-- v2: indexed bank/group selection, avoiding large selected-OR reductions.
|
|||
---------------------------------------------------------------------------
|
|||
LIBRARY ieee;
|
|||
USE ieee.std_logic_1164.ALL;
|
|||
USE ieee.numeric_std.ALL;
|
|||
ENTITY m9k_grouped IS
|
|||
GENERIC
|
|||
(
|
|||
-- 64K configuration:
|
|||
-- (7*8 + 7)*1024 = 63 KiB in 56 M9Ks using the 9th bit
|
|||
-- +1 KiB in one extra M9K
|
|||
NUM_GROUPS : natural := 7;
|
|||
EXTRA_RAM_BLOCKS : natural := 1
|
|||
-- 48K configuration:
|
|||
-- NUM_GROUPS : natural := 5;
|
|||
-- EXTRA_RAM_BLOCKS : natural := 3
|
|||
);
|
|||
PORT
|
|||
(
|
|||
clock : IN std_logic;
|
|||
reset_n : IN std_logic := '1';
|
|||
data : IN std_logic_vector(7 DOWNTO 0);
|
|||
address : IN std_logic_vector(15 DOWNTO 0);
|
|||
we : IN std_logic;
|
|||
q : OUT std_logic_vector(7 DOWNTO 0)
|
|||
);
|
|||
END m9k_grouped;
|
|||
ARCHITECTURE rtl OF m9k_grouped IS
|
|||
constant ADDR_BITS : natural := 16;
|
|||
constant RAM_ADDR_BITS : natural := 10; -- 1 KiB per RAM block
|
|||
constant PAGE_BITS : natural := ADDR_BITS - RAM_ADDR_BITS; -- 6
|
|||
constant BYTE_BITS : natural := 8;
|
|||
constant WIDE_BIT : natural := 8;
|
|||
constant NORMAL_RAM_BLOCKS : natural := NUM_GROUPS * BYTE_BITS;
|
|||
constant GROUP_RAM_PAGES : natural := NORMAL_RAM_BLOCKS + NUM_GROUPS;
|
|||
constant TOTAL_RAM_BLOCKS : natural := NORMAL_RAM_BLOCKS + EXTRA_RAM_BLOCKS;
|
|||
constant TOTAL_MAPPED_PAGES: natural := GROUP_RAM_PAGES + EXTRA_RAM_BLOCKS;
|
|||
TYPE ram_data_t IS ARRAY(0 TO TOTAL_RAM_BLOCKS-1) OF std_logic_vector(8 DOWNTO 0);
|
|||
SIGNAL q_ram_wide : ram_data_t;
|
|||
SIGNAL write_data_ram_wide : ram_data_t;
|
|||
SIGNAL sel_ram : std_logic_vector(0 TO TOTAL_RAM_BLOCKS-1);
|
|||
SIGNAL wide_ram : std_logic;
|
|||
SIGNAL we_ram : std_logic;
|
|||
SIGNAL address_ram : std_logic_vector(RAM_ADDR_BITS-1 DOWNTO 0);
|
|||
SIGNAL address_used : std_logic_vector(ADDR_BITS-1 DOWNTO 0);
|
|||
SIGNAL state_next : std_logic_vector(0 DOWNTO 0);
|
|||
SIGNAL state_reg : std_logic_vector(0 DOWNTO 0);
|
|||
constant state_idle : std_logic_vector(0 DOWNTO 0) := "0";
|
|||
constant state_write : std_logic_vector(0 DOWNTO 0) := "1";
|
|||
SIGNAL data_next : std_logic_vector(7 DOWNTO 0);
|
|||
SIGNAL data_reg : std_logic_vector(7 DOWNTO 0);
|
|||
SIGNAL address_next : std_logic_vector(ADDR_BITS-1 DOWNTO 0);
|
|||
SIGNAL address_reg : std_logic_vector(ADDR_BITS-1 DOWNTO 0);
|
|||
BEGIN
|
|||
-- During idle/read, the RAMs see the live bus address.
|
|||
-- During the write cycle, they see the latched write address.
|
|||
address_used <= address_reg WHEN state_reg = state_write ELSE address;
|
|||
address_ram <= address_used(RAM_ADDR_BITS-1 DOWNTO 0);
|
|||
PROCESS(clock, reset_n)
|
|||
BEGIN
|
|||
IF reset_n = '0' THEN
|
|||
state_reg <= state_idle;
|
|||
data_reg <= (others => '0');
|
|||
address_reg <= (others => '0');
|
|||
ELSIF rising_edge(clock) THEN
|
|||
state_reg <= state_next;
|
|||
data_reg <= data_next;
|
|||
address_reg <= address_next;
|
|||
END IF;
|
|||
END PROCESS;
|
|||
-- Latch write address/data for one-cycle read-modify-write.
|
|||
PROCESS(state_reg, we, data, address, data_reg, address_reg)
|
|||
BEGIN
|
|||
state_next <= state_reg;
|
|||
data_next <= data_reg;
|
|||
address_next <= address_reg;
|
|||
we_ram <= '0';
|
|||
CASE state_reg IS
|
|||
WHEN state_idle =>
|
|||
IF we = '1' THEN
|
|||
data_next <= data;
|
|||
address_next <= address;
|
|||
state_next <= state_write;
|
|||
END IF;
|
|||
WHEN state_write =>
|
|||
we_ram <= '1';
|
|||
state_next <= state_idle;
|
|||
WHEN others =>
|
|||
state_next <= state_idle;
|
|||
END CASE;
|
|||
END PROCESS;
|
|||
m9k_loop: FOR i IN 0 TO TOTAL_RAM_BLOCKS-1 GENERATE
|
|||
sample_ram_inst : ENTITY work.generic_ram_infer
|
|||
GENERIC MAP
|
|||
(
|
|||
ADDRESS_WIDTH => RAM_ADDR_BITS,
|
|||
SPACE => 1024,
|
|||
DATA_WIDTH => 9
|
|||
)
|
|||
PORT MAP
|
|||
(
|
|||
clock => clock,
|
|||
reset_n => reset_n,
|
|||
data => write_data_ram_wide(i),
|
|||
address => address_ram,
|
|||
we => sel_ram(i) AND we_ram,
|
|||
q => q_ram_wide(i)
|
|||
);
|
|||
END GENERATE m9k_loop;
|
|||
-- Decode only the write-enable selection and the mode.
|
|||
-- Reads use direct indexed muxes rather than sel_ram-masked OR reductions.
|
|||
PROCESS(address_used)
|
|||
VARIABLE page : natural RANGE 0 TO 2**PAGE_BITS-1;
|
|||
VARIABLE wide_group : natural RANGE 0 TO BYTE_BITS-1;
|
|||
VARIABLE extra : natural RANGE 0 TO EXTRA_RAM_BLOCKS;
|
|||
BEGIN
|
|||
page := to_integer(unsigned(address_used(ADDR_BITS-1 DOWNTO RAM_ADDR_BITS)));
|
|||
wide_group := to_integer(unsigned(address_used(12 DOWNTO 10)));
|
|||
wide_ram <= '0';
|
|||
sel_ram <= (others => '0');
|
|||
-- 64 KiB example:
|
|||
-- pages 0..55 : normal 8-bit access to RAM blocks 0..55
|
|||
-- pages 56..62 : wide access using bit 8 of RAM blocks 0..55
|
|||
-- page 63 : normal 8-bit access to RAM block 56
|
|||
IF page < NORMAL_RAM_BLOCKS THEN
|
|||
sel_ram(page) <= '1';
|
|||
ELSIF page < GROUP_RAM_PAGES THEN
|
|||
wide_ram <= '1';
|
|||
IF wide_group < NUM_GROUPS THEN
|
|||
FOR bt IN 0 TO BYTE_BITS-1 LOOP
|
|||
sel_ram((wide_group * BYTE_BITS) + bt) <= '1';
|
|||
END LOOP;
|
|||
END IF;
|
|||
ELSIF page < TOTAL_MAPPED_PAGES THEN
|
|||
extra := page - GROUP_RAM_PAGES;
|
|||
sel_ram(NORMAL_RAM_BLOCKS + extra) <= '1';
|
|||
END IF;
|
|||
END PROCESS;
|
|||
-- RAM write data.
|
|||
-- Only the selected RAMs are written, so unselected RAM data inputs are don't-care.
|
|||
-- The assignments below avoid copying every full RAM word back to its input.
|
|||
write_data_loop: FOR i IN 0 TO TOTAL_RAM_BLOCKS-1 GENERATE
|
|||
PROCESS(wide_ram, address_used, data_reg, q_ram_wide)
|
|||
VARIABLE wide_group : natural RANGE 0 TO BYTE_BITS-1;
|
|||
BEGIN
|
|||
wide_group := to_integer(unsigned(address_used(12 DOWNTO 10)));
|
|||
write_data_ram_wide(i) <= (others => '0');
|
|||
IF wide_ram = '0' THEN
|
|||
-- Normal byte write: replace bits 0..7, preserve the packed wide bit.
|
|||
write_data_ram_wide(i)(BYTE_BITS-1 DOWNTO 0) <= data_reg;
|
|||
write_data_ram_wide(i)(WIDE_BIT) <= q_ram_wide(i)(WIDE_BIT);
|
|||
ELSE
|
|||
-- Wide write: selected group of 8 RAMs stores one bit each in bit 8.
|
|||
-- Preserve the normal byte only for RAMs in the selected group.
|
|||
IF i < NORMAL_RAM_BLOCKS THEN
|
|||
IF wide_group = (i / BYTE_BITS) THEN
|
|||
write_data_ram_wide(i)(BYTE_BITS-1 DOWNTO 0) <= q_ram_wide(i)(BYTE_BITS-1 DOWNTO 0);
|
|||
write_data_ram_wide(i)(WIDE_BIT) <= data_reg(i MOD BYTE_BITS);
|
|||
END IF;
|
|||
END IF;
|
|||
END IF;
|
|||
END PROCESS;
|
|||
END GENERATE write_data_loop;
|
|||
-- Read mux. This replaces the previous 57-way selected OR-reduction fabric.
|
|||
PROCESS(address_used, q_ram_wide)
|
|||
VARIABLE page : natural RANGE 0 TO 2**PAGE_BITS-1;
|
|||
VARIABLE wide_group : natural RANGE 0 TO BYTE_BITS-1;
|
|||
VARIABLE extra : natural RANGE 0 TO EXTRA_RAM_BLOCKS;
|
|||
BEGIN
|
|||
page := to_integer(unsigned(address_used(ADDR_BITS-1 DOWNTO RAM_ADDR_BITS)));
|
|||
wide_group := to_integer(unsigned(address_used(12 DOWNTO 10)));
|
|||
q <= (others => '0');
|
|||
IF page < NORMAL_RAM_BLOCKS THEN
|
|||
q <= q_ram_wide(page)(BYTE_BITS-1 DOWNTO 0);
|
|||
ELSIF page < GROUP_RAM_PAGES THEN
|
|||
IF wide_group < NUM_GROUPS THEN
|
|||
FOR bt IN 0 TO BYTE_BITS-1 LOOP
|
|||
q(bt) <= q_ram_wide((wide_group * BYTE_BITS) + bt)(WIDE_BIT);
|
|||
END LOOP;
|
|||
END IF;
|
|||
ELSIF page < TOTAL_MAPPED_PAGES THEN
|
|||
extra := page - GROUP_RAM_PAGES;
|
|||
q <= q_ram_wide(NORMAL_RAM_BLOCKS + extra)(BYTE_BITS-1 DOWNTO 0);
|
|||
END IF;
|
|||
END PROCESS;
|
|||
END rtl;
|