Add VHDL code

This commit is contained in:
Joris van Rantwijk 2024-08-02 21:47:58 +02:00
parent 12bcf4e4a9
commit 6b5f2967ac
4 changed files with 1231 additions and 0 deletions

556
fpga/rtl/dma_axi_master.vhd Normal file
View File

@ -0,0 +1,556 @@
--
-- AXI3 master for multi-channel DMA controller.
--
-- Joris van Rantwijk 2024
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.puzzlefw_pkg.all;
entity dma_axi_master is
generic (
-- Number of beats per transfer.
transfer_size: integer range 1 to 16 := 16;
-- Number of read channels.
num_read_channels: integer range 0 to 16;
-- Number of write channels.
num_write_channels: integer range 0 to 16
);
port (
-- Main clock, active on rising edge.
clk: in std_logic;
-- Reset, active high, synchronous to main clock.
reset: in std_logic;
-- High to enable DMA, low to pause DMA.
-- When dma_en is low, any ongoing transfer will be completed but no new transfer will be started.
dma_en: in std_logic;
-- High while DMA transactions are in progress.
dma_busy: out std_logic;
-- Address window in which DMA transaction can occur.
-- The base address is added to the addresses requested by the channels.
window_base_addr: in std_logic_vector(31 downto 12);
window_size: in std_logic_vector(31 downto 12);
-- Error notifications.
-- If an error occurs, the corresponding error signal will go high and stay high until cleared.
-- After error, further DMA transactions will be halted until the error is cleared.
-- Note that DMA errors will typically cause misalignment of the data flow in client channels.
err_read: out std_logic; -- AXI slave reported error from read transaction
err_write: out std_logic; -- AXI slave reported error from write transaction
err_address: out std_logic; -- Channel requested address outside window
err_any: out std_logic; -- Logical OR of all error signals
-- High to clear error notifications.
clear_errors: in std_logic;
-- Read channels.
-- The client passes a read command for a specified address via valid/ready handshake.
-- Some time later, the controller pushes (transfer_size) data words out to the channel.
-- The client must be ready to accept all data words.
-- Multiple transfers can be in flight for the channel.
read_cmd_addr: in dma_address_array(0 to num_read_channels-1);
read_cmd_valid: in std_logic_vector(num_read_channels-1 downto 0);
read_cmd_ready: out std_logic_vector(num_read_channels-1 downto 0);
read_data: out dma_data_array(0 to num_read_channels-1);
read_data_valid: out std_logic_vector(num_read_channels-1 downto 0);
-- Write channels.
-- The client passes a write command for a specified address via valid/ready handshake.
-- Some time later, the controller pulls (transfer_size) data words from the channel.
-- The client must supply these data words promptly.
-- Some more time later, the controller pulses write_finished to indicate that the write has finished.
-- Multiple transfers can be in flight for the channel.
write_cmd_addr: in dma_address_array(0 to num_write_channels-1);
write_cmd_valid: in std_logic_vector(num_write_channels-1 downto 0);
write_cmd_ready: out std_logic_vector(num_write_channels-1 downto 0);
write_data: in dma_data_array(0 to num_write_channels-1);
write_data_ready: out std_logic_vector(num_write_channels-1 downto 0);
write_finished: out std_logic_vector(num_write_channels-1 downto 0);
-- AXI3 master.
m_axi_awid: out std_logic_vector(5 downto 0);
m_axi_awaddr: out std_logic_vector(31 downto 0);
m_axi_awlen: out std_logic_vector(3 downto 0);
m_axi_awsize: out std_logic_vector(2 downto 0);
m_axi_awburst: out std_logic_vector(1 downto 0);
m_axi_awlock: out std_logic_vector(1 downto 0);
m_axi_awcache: out std_logic_vector(3 downto 0);
m_axi_awprot: out std_logic_vector(2 downto 0);
m_axi_awqos: out std_logic_vector(3 downto 0);
m_axi_awvalid: out std_logic;
m_axi_awready: in std_logic;
m_axi_wid: out std_logic_vector(5 downto 0);
m_axi_wdata: out std_logic_vector(63 downto 0);
m_axi_wstrb: out std_logic_vector(7 downto 0);
m_axi_wlast: out std_logic;
m_axi_wvalid: out std_logic;
m_axi_wready: in std_logic;
m_axi_bid: in std_logic_vector(5 downto 0);
m_axi_bresp: in std_logic_vector(1 downto 0);
m_axi_bvalid: in std_logic;
m_axi_bready: out std_logic;
m_axi_arid: out std_logic_vector(5 downto 0);
m_axi_araddr: out std_logic_vector(31 downto 0);
m_axi_arlen: out std_logic_vector(3 downto 0);
m_axi_arsize: out std_logic_vector(2 downto 0);
m_axi_arburst: out std_logic_vector(1 downto 0);
m_axi_arlock: out std_logic_vector(1 downto 0);
m_axi_arcache: out std_logic_vector(3 downto 0);
m_axi_arprot: out std_logic_vector(2 downto 0);
m_axi_arqos: out std_logic_vector(3 downto 0);
m_axi_arvalid: out std_logic;
m_axi_arready: in std_logic;
m_axi_rid: in std_logic_vector(5 downto 0);
m_axi_rdata: in std_logic_vector(63 downto 0);
m_axi_rresp: in std_logic_vector(1 downto 0);
m_axi_rlast: in std_logic;
m_axi_rvalid: in std_logic;
m_axi_rready: out std_logic
);
end entity;
architecture arch of dma_axi_master is
type write_state_type is (WRITE_STATE_IDLE, WRITE_STATE_START, WRITE_STATE_FLOW, WRITE_STATE_WAIT);
type read_state_type is (READ_STATE_IDLE, READ_STATE_START, READ_STATE_WAIT);
type regs_type is record
-- Registered output signals to AXI bus.
awaddr: std_logic_vector(31 downto 3);
awvalid: std_logic;
wdata: std_logic_vector(63 downto 0);
wlast: std_logic;
wvalid: std_logic;
araddr: std_logic_vector(31 downto 3);
arvalid: std_logic;
-- Registered output signals to read channels.
read_cmd_ready: std_logic_vector(num_read_channels-1 downto 0);
read_data: std_logic_vector(63 downto 0);
read_data_valid: std_logic_vector(num_read_channels-1 downto 0);
-- Registered output signals to write channels.
write_cmd_ready: std_logic_vector(num_write_channels-1 downto 0);
write_finished: std_logic_vector(num_write_channels-1 downto 0);
-- Registered status output signals.
dma_busy: std_logic;
err_read: std_logic;
err_write: std_logic;
err_address: std_logic;
err_any: std_logic;
-- Write state machine.
write_state: write_state_type;
write_channel: integer range 0 to maximum(0, num_write_channels - 1);
write_channel_mask: std_logic_vector(num_write_channels-1 downto 0);
cnt_write_beat: unsigned(3 downto 0);
cnt_write_start: unsigned(5 downto 0);
cnt_write_end: unsigned(5 downto 0);
-- Read command state machine.
read_state: read_state_type;
read_channel: integer range 0 to maximum(0, num_read_channels - 1);
cnt_read_start: unsigned(5 downto 0);
cnt_read_end: unsigned(5 downto 0);
end record;
constant regs_init: regs_type := (
awaddr => (others => '0'),
awvalid => '0',
wdata => (others => '0'),
wlast => '0',
wvalid => '0',
araddr => (others => '0'),
arvalid => '0',
read_cmd_ready => (others => '0'),
read_data => (others => '0'),
read_data_valid => (others => '0'),
write_cmd_ready => (others => '0'),
write_finished => (others => '0'),
dma_busy => '0',
err_read => '0',
err_write => '0',
err_address => '0',
err_any => '0',
write_state => WRITE_STATE_IDLE,
write_channel => 0,
write_channel_mask => (others => '0'),
cnt_write_beat => (others => '0'),
cnt_write_start => (others => '0'),
cnt_write_end => (others => '0'),
read_state => READ_STATE_IDLE,
read_channel => 0,
cnt_read_start => (others => '0'),
cnt_read_end => (others => '0')
);
signal r: regs_type := regs_init;
signal rnext: regs_type;
-- Check that the DMA transfer fits inside the address window if it starts at the specified address offset
function is_valid_dma_address(addr: std_logic_vector(31 downto 3);
limit: std_logic_vector(31 downto 12))
return boolean
is begin
return (unsigned(limit) /= 0) and (unsigned(addr) <= shift_left(unsigned(limit), 9) - transfer_size);
end function;
-- Calculate tha AXI address for a DMA transfer by adding the address offset from the client
-- to the base address of the DMA window.
-- Returns the 29 most significant address bits; the 3 least significant bits are presumed to be 0.
function calc_dma_address(addr: std_logic_vector(31 downto 3);
base_addr: std_logic_vector(31 downto 12))
return std_logic_vector
is begin
return std_logic_vector(unsigned(addr) + shift_left(resize(unsigned(base_addr), 29), 9));
end function;
begin
-- Drive fixed output signals to AXI bus.
m_axi_awlen <= std_logic_vector(to_unsigned(transfer_size - 1, 4)); -- use fixed burst length
m_axi_arlen <= std_logic_vector(to_unsigned(transfer_size - 1, 4)); -- use fixed burst length
m_axi_awsize <= "011"; -- always use 64-bit transfers
m_axi_arsize <= "011"; -- always use 64-bit transfers
m_axi_awburst <= "01"; -- always use incrementing burst
m_axi_arburst <= "01"; -- always use incrementing burst
m_axi_awlock <= "00"; -- normal access
m_axi_arlock <= "00"; -- normal access
m_axi_awcache <= "0010"; -- normal memory, non-cacheable, non-bufferable
m_axi_arcache <= "0010"; -- normal memory, non-cacheable, non-bufferable
m_axi_awprot <= "000"; -- data access, secure, unprivileged
m_axi_arprot <= "000"; -- data access, secure, unprivileged
m_axi_awqos <= "0000"; -- no QoS
m_axi_arqos <= "0000"; -- no QoS
m_axi_wstrb <= "11111111"; -- always write all byte lanes
m_axi_bready <= '1'; -- always ready for write response
m_axi_rready <= '1'; -- always ready for read response
-- Drive variable output signals to AXI bus.
m_axi_awid <= std_logic_vector(to_unsigned(r.write_channel, 6));
m_axi_awaddr <= r.awaddr & "000"; -- addresses are 8-byte aligned
m_axi_awvalid <= r.awvalid;
m_axi_wid <= std_logic_vector(to_unsigned(r.write_channel, 6));
m_axi_wdata <= r.wdata;
m_axi_wlast <= r.wlast;
m_axi_wvalid <= r.wvalid;
m_axi_arid <= std_logic_vector(to_unsigned(r.read_channel, 6));
m_axi_araddr <= r.araddr & "000"; -- addresses are 8-byte aligned
m_axi_arvalid <= r.arvalid;
-- Drive output signals to read channels.
read_cmd_ready <= r.read_cmd_ready;
read_data_valid <= r.read_data_valid;
gen_rdata: for ch in 0 to num_read_channels - 1 generate
read_data(ch) <= r.read_data;
end generate;
-- Drive output signals to write channels.
write_cmd_ready <= r.write_cmd_ready;
write_finished <= r.write_finished;
-- Write data ready signalling is complicated:
-- - During WRITE_STATE_START, one write_data_ready cycle is sent to the selected channel.
-- - During WRITE_STATE_FLOW, write_data_ready to the selected channel depends asynchronously on AXI wready.
write_data_ready <=
r.write_channel_mask
when ((r.write_state = WRITE_STATE_START) or (r.write_state = WRITE_STATE_FLOW and m_axi_wready = '1'))
else (others => '0');
-- Drive output signals.
dma_busy <= r.dma_busy;
err_read <= r.err_read;
err_write <= r.err_write;
err_address <= r.err_address;
err_any <= r.err_any;
--
-- Combinatorial process.
--
process (all) is
variable v: regs_type;
begin
-- Load current register values.
v := r;
-- Report DMA busy/idle.
if (r.cnt_write_start = r.cnt_write_end) and (r.cnt_read_start = r.cnt_read_end) then
v.dma_busy := '0';
else
v.dma_busy := '1';
end if;
-- Clear pending errors.
if clear_errors = '1' then
v.err_read := '0';
v.err_write := '0';
v.err_address := '0';
v.err_any := '0';
end if;
--
-- Write state machine.
--
if num_write_channels > 0 then
-- By default, do not accept write commands from any channel.
v.write_cmd_ready := (others => '0');
-- Maintain one-hot write channel mask for the selected write channel.
-- This register is needed during WRITE_STATE_START and WRITE_STATE_FLOW.
v.write_channel_mask := (others => '0');
v.write_channel_mask(r.write_channel) := '1';
case r.write_state is
when WRITE_STATE_IDLE =>
-- Cycle through write channels until we find a channel that wants to write.
if (dma_en = '1') and (r.err_any = '0') then
if write_cmd_valid(r.write_channel) = '1' then
-- This channel wants to read. Let's do it.
v.write_state := WRITE_STATE_START;
v.write_cmd_ready(r.write_channel) := '1';
v.dma_busy := '1';
else
-- Move on to the next channel.
if r.write_channel >= num_write_channels - 1 then
v.write_channel := 0;
else
v.write_channel := r.write_channel + 1;
end if;
end if;
end if;
when WRITE_STATE_START =>
-- Calculate the AXI address by adding the client address offset to the window base address.
v.awaddr := calc_dma_address(write_cmd_addr(r.write_channel), window_base_addr);
-- Setup first data word.
v.wdata := write_data(r.write_channel);
v.cnt_write_beat := (others => '0');
if transfer_size = 1 then
v.wlast := '1';
else
v.wlast := '0';
end if;
-- Check address.
if is_valid_dma_address(write_cmd_addr(r.write_channel), window_size) then
-- Setup AXI write burst.
v.awvalid := '1';
v.wvalid := '1';
v.cnt_write_start := r.cnt_write_start + 1;
if transfer_size = 1 then
v.write_state := WRITE_STATE_WAIT;
else
v.write_state := WRITE_STATE_FLOW;
end if;
else
-- Report invalid address.
-- At this point, the client channel will be stuck with misaligned write command
-- and data channels, and this write transfer will never be confirmed.
-- To recover, some type of reset of the client channel will be necessary.
v.err_address := '1';
v.err_any := '1';
v.write_state := WRITE_STATE_IDLE;
end if;
-- Mark DMA busy.
v.dma_busy := '1';
when WRITE_STATE_FLOW =>
-- Push subsequent data words to the interconnect.
-- Drop write command when accepted by interconnect.
if m_axi_awready = '1' then
v.awvalid := '0';
end if;
-- Push data words to interconnect.
if m_axi_wready = '1' then
v.wdata := write_data(r.write_channel);
if r.cnt_write_beat = transfer_size - 2 then
-- This will be the last beat of the transfer.
v.wlast := '1';
v.write_state := WRITE_STATE_WAIT;
else
v.wlast := '0';
end if;
v.cnt_write_beat := r.cnt_write_beat + 1;
end if;
when WRITE_STATE_WAIT =>
-- Wait until interconnect accepts the last beat.
if m_axi_awready = '1' then
v.awvalid := '0';
end if;
if m_axi_wready = '1' then
v.wvalid := '0';
end if;
if (r.awvalid = '0' or m_axi_awready = '1') and (r.wvalid = '0' or m_axi_wready = '1') then
v.write_state := WRITE_STATE_IDLE;
end if;
end case;
end if;
--
-- Handle write completion.
--
-- Report write completion to the channel client.
-- Only successful write bursts are reported.
-- Note that a write error will cause misalignment between command and data flow in the channel.
for i in 0 to num_write_channels - 1 loop
if (m_axi_bvalid = '1') and (m_axi_bresp(1) = '0') and (unsigned(m_axi_bid(3 downto 0)) = i) then
v.write_finished(i) := '1';
else
v.write_finished(i) := '0';
end if;
end loop;
-- Detect write errors.
if (m_axi_bvalid = '1') and (m_axi_bresp(1) = '1') then
v.err_write := '1';
v.err_any := '1';
end if;
-- Count write burst completed.
if m_axi_bvalid = '1' then
v.cnt_write_end := r.cnt_write_end + 1;
end if;
--
-- Read command state machine.
--
if num_read_channels > 0 then
-- By default, do not accept read commands from any channel.
v.read_cmd_ready := (others => '0');
case r.read_state is
when READ_STATE_IDLE =>
-- Cycle through read channels until we find a channel that wants to read.
if (dma_en = '1') and (r.err_any = '0') then
if read_cmd_valid(r.read_channel) = '1' then
-- This channel wants to read. Let's do it.
v.read_state := READ_STATE_START;
v.read_cmd_ready(r.read_channel) := '1';
v.dma_busy := '1';
else
-- Move on to the next channel.
if r.read_channel >= num_read_channels - 1 then
v.read_channel := 0;
else
v.read_channel := r.read_channel + 1;
end if;
end if;
end if;
when READ_STATE_START =>
-- Calculate the AXI address by adding the client address offset to the window base address.
v.araddr := calc_dma_address(read_cmd_addr(r.read_channel), window_base_addr);
-- Check address.
if is_valid_dma_address(read_cmd_addr(r.read_channel), window_size) then
-- Setup AXI read burst.
v.arvalid := '1';
v.read_state := READ_STATE_WAIT;
else
-- Report invalid address.
-- At this point, the client channel will be stuck waiting for results
-- that are never going to arrive.
-- To recover, some type of reset of the client channel will be necessary.
v.err_address := '1';
v.err_any := '1';
v.read_state := READ_STATE_IDLE;
end if;
-- Mark DMA busy.
v.dma_busy := '1';
when READ_STATE_WAIT =>
-- Wait until interconnect accepts our read burst.
v.dma_busy := '1';
if m_axi_arready = '1' then
-- Read burst accepted.
v.arvalid := '0';
v.cnt_read_start := r.cnt_read_start + 1;
v.read_state := READ_STATE_IDLE;
end if;
end case;
end if;
--
-- Handle read result.
--
-- Latch read result in a register.
if m_axi_rvalid = '1' then
v.read_data := m_axi_rdata;
end if;
-- Report read data to the channel client.
-- Only successful read results are reported.
-- Note than a read error will cause misalignment between command and data flow in the channel.
for i in 0 to num_read_channels - 1 loop
if (m_axi_rvalid = '1') and (m_axi_rresp(1) = '0') and (unsigned(m_axi_rid(3 downto 0)) = i) then
v.read_data_valid(i) := '1';
else
v.read_data_valid(i) := '0';
end if;
end loop;
-- Detect read errors.
if (m_axi_rvalid = '1') and (m_axi_rresp(1) = '1') then
v.err_read := '1';
v.err_any := '1';
end if;
-- Count read burst completed.
if m_axi_rvalid = '1' and m_axi_rlast = '1' then
v.cnt_read_end := r.cnt_read_end + 1;
end if;
-- Synchronous reset.
if reset = '1' then
v := regs_init;
end if;
-- Drive new register values to synchronous process.
rnext <= v;
end process;
--
-- Synchronous process.
--
process (clk) is
begin
if rising_edge(clk) then
r <= rnext;
end if;
end process;
end architecture;

91
fpga/rtl/puzzlefw_pkg.vhd Normal file
View File

@ -0,0 +1,91 @@
--
-- Global definitions for Red Pitaya PuzzleFW firmware.
--
-- Joris van Rantwijk 2024
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
package puzzlefw_pkg is
-- 32-bit address for DMA on AXI bus, aligned to 8-byte multiple.
subtype dma_address_type is std_logic_vector(31 downto 3);
type dma_address_array is array(natural range <>) of dma_address_type;
-- 64-bit data for DMA on AXI bus.
subtype dma_data_type is std_logic_vector(63 downto 0);
type dma_data_array is array(natural range <>) of dma_data_type;
-- Register addresses.
constant reg_addr_mask: std_logic_vector(31 downto 0) := x"0010fffc";
constant reg_info: natural := 16#000000#;
constant reg_irq_enable: natural := 16#000010#;
constant reg_dma_en: natural := 16#000100#;
constant reg_dma_status: natural := 16#000104#;
constant reg_dma_clear: natural := 16#000108#;
constant reg_rcnt: natural := 16#000200#;
constant reg_wcnt: natural := 16#000204#;
constant reg_start: natural := 16#000208#;
constant reg_dma_buf_addr: natural := 16#100000#;
constant reg_dma_buf_size: natural := 16#100004#;
constant reg_test_irq: natural := 16#100100#;
constant reg_test_led: natural := 16#100104#;
-- Firmware info word.
constant fw_api_version: natural := 1;
constant fw_version_major: natural := 0;
constant fw_version_minor: natural := 2;
constant fw_info_word: std_logic_vector(31 downto 0) :=
x"4a"
& std_logic_vector(to_unsigned(fw_api_version, 8))
& std_logic_vector(to_unsigned(fw_version_major, 8))
& std_logic_vector(to_unsigned(fw_version_minor, 8));
-- ADC input port type.
type adc_data_input_type is array(0 to 1) of std_logic_vector(15 downto 0);
-- Control registers: read/write access by processor, output signals to FPGA.
type registers_control is record
irq_enable: std_logic;
test_irq: std_logic_vector(7 downto 0);
test_led: std_logic_vector(7 downto 0);
dma_en: std_logic;
dma_buf_addr: std_logic_vector(31 downto 12);
dma_buf_size: std_logic_vector(31 downto 12);
end record;
-- Status registers: input signals from FPGA, read-only access by processor.
type registers_status is record
dma_busy: std_logic;
dma_err_read: std_logic;
dma_err_write: std_logic;
dma_err_address: std_logic;
dma_err_any: std_logic;
rcnt: unsigned(31 downto 0);
wcnt: unsigned(31 downto 0);
end record;
-- Trigger registers: write-only access by processor, single-cycle pulse signals to FPGA.
type registers_trigger is record
dma_clear: std_logic;
start: std_logic;
end record;
constant registers_control_init: registers_control := (
irq_enable => '0',
test_irq => (others => '0'),
test_led => (others => '0'),
dma_en => '0',
dma_buf_addr => (others => '0'),
dma_buf_size => (others => '0')
);
constant registers_trigger_init: registers_trigger := (
dma_clear => '0',
start => '0'
);
end package;

440
fpga/rtl/puzzlefw_top.vhd Normal file
View File

@ -0,0 +1,440 @@
--
-- Top-level FPGA design for Red Pitaya PuzzleFW firmware.
--
-- Joris van Rantwijk 2024
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library unisim;
use unisim.vcomponents.all;
library xpm;
use xpm.vcomponents.all;
use work.puzzlefw_pkg.all;
entity puzzlefw_top is
port (
-- Ports directly connected to ARM/PS.
DDR_0_addr: inout std_logic_vector(14 downto 0);
DDR_0_ba: inout std_logic_vector(2 downto 0);
DDR_0_cas_n: inout std_logic;
DDR_0_ck_n: inout std_logic;
DDR_0_ck_p: inout std_logic;
DDR_0_cke: inout std_logic;
DDR_0_cs_n: inout std_logic;
DDR_0_dm: inout std_logic_vector(3 downto 0);
DDR_0_dq: inout std_logic_vector(31 downto 0);
DDR_0_dqs_n: inout std_logic_vector(3 downto 0);
DDR_0_dqs_p: inout std_logic_vector(3 downto 0);
DDR_0_odt: inout std_logic;
DDR_0_ras_n: inout std_logic;
DDR_0_reset_n: inout std_logic;
DDR_0_we_n: inout std_logic;
FIXED_IO_0_ddr_vrn: inout std_logic;
FIXED_IO_0_ddr_vrp: inout std_logic;
FIXED_IO_0_mio: inout std_logic_vector(53 downto 0);
FIXED_IO_0_ps_clk: inout std_logic;
FIXED_IO_0_ps_porb: inout std_logic;
FIXED_IO_0_ps_srstb: inout std_logic;
-- Ports controlled by FPGA.
adc_dat_i: in adc_data_input_type; -- ADC data
adc_clk_i: in std_logic_vector(1 downto 0); -- ADC clock 1=pos, 0=neg
adc_clk_o: out std_logic_vector(1 downto 0); -- optional clock output for ADC
adc_cdcs_o: out std_logic; -- ADC clock duty cycle stabilizer
dac_dat_o: out std_logic_vector(13 downto 0); -- DAC data
dac_wrt_o: out std_logic; -- DAC write control
dac_sel_o: out std_logic; -- DAC channel select
dac_clk_o: out std_logic; -- DAC clock
dac_rst_o: out std_logic; -- DAC reset
dac_pwm_o: out std_logic_vector(3 downto 0); -- PWM DAC
exp_p_io: inout std_logic_vector(7 downto 0); -- extension I/O pos
exp_n_io: inout std_logic_vector(7 downto 0); -- extension I/O neg
led_o: inout std_logic_vector(7 downto 0) -- LEDs
);
end puzzlefw_top;
architecture arch of puzzlefw_top is
-- Main 125 MHz clock, derived from ADC clock input port.
signal clk_adc: std_logic;
-- Auxiliary clock from FCLK0.
signal clk_fclk: std_logic;
-- Main reset signal, derived from FCLK_RESET0, active high, synchronous to clk_adc.
signal s_reset: std_logic;
signal s_adc_clk_ibuf: std_logic;
signal r_fclk_cnt: unsigned(26 downto 0);
signal r_fclk_led: std_logic;
signal r_adcclk_cnt: unsigned(26 downto 0);
signal r_adcclk_led: std_logic;
signal s_apb_paddr: std_logic_vector(31 downto 0);
signal s_apb_penable: std_logic;
signal s_apb_prdata: std_logic_vector(31 downto 0);
signal s_apb_pready: std_logic;
signal s_apb_psel: std_logic;
signal s_apb_pslverr: std_logic;
signal s_apb_pwdata: std_logic_vector(31 downto 0);
signal s_apb_pwrite: std_logic;
signal s_axi_awid: std_logic_vector(5 downto 0);
signal s_axi_awaddr: std_logic_vector(31 downto 0);
signal s_axi_awlen: std_logic_vector(3 downto 0);
signal s_axi_awsize: std_logic_vector(2 downto 0);
signal s_axi_awburst: std_logic_vector(1 downto 0);
signal s_axi_awlock: std_logic_vector(1 downto 0);
signal s_axi_awcache: std_logic_vector(3 downto 0);
signal s_axi_awprot: std_logic_vector(2 downto 0);
signal s_axi_awqos: std_logic_vector(3 downto 0);
signal s_axi_awvalid: std_logic;
signal s_axi_awready: std_logic;
signal s_axi_wid: std_logic_vector(5 downto 0);
signal s_axi_wdata: std_logic_vector(63 downto 0);
signal s_axi_wstrb: std_logic_vector(7 downto 0);
signal s_axi_wlast: std_logic;
signal s_axi_wvalid: std_logic;
signal s_axi_wready: std_logic;
signal s_axi_bid: std_logic_vector(5 downto 0);
signal s_axi_bresp: std_logic_vector(1 downto 0);
signal s_axi_bvalid: std_logic;
signal s_axi_bready: std_logic;
signal s_axi_arid: std_logic_vector(5 downto 0);
signal s_axi_araddr: std_logic_vector(31 downto 0);
signal s_axi_arlen: std_logic_vector(3 downto 0);
signal s_axi_arsize: std_logic_vector(2 downto 0);
signal s_axi_arburst: std_logic_vector(1 downto 0);
signal s_axi_arlock: std_logic_vector(1 downto 0);
signal s_axi_arcache: std_logic_vector(3 downto 0);
signal s_axi_arprot: std_logic_vector(2 downto 0);
signal s_axi_arqos: std_logic_vector(3 downto 0);
signal s_axi_arvalid: std_logic;
signal s_axi_arready: std_logic;
signal s_axi_rid: std_logic_vector(5 downto 0);
signal s_axi_rdata: std_logic_vector(63 downto 0);
signal s_axi_rresp: std_logic_vector(1 downto 0);
signal s_axi_rlast: std_logic;
signal s_axi_rvalid: std_logic;
signal s_axi_rready: std_logic;
signal s_irq: std_logic_vector(7 downto 0);
signal s_reg_control: registers_control;
signal s_reg_status: registers_status;
signal s_reg_trigger: registers_trigger;
signal s_dma_read_cmd_addr: dma_address_type;
signal s_dma_read_cmd_valid: std_logic;
signal s_dma_read_cmd_ready: std_logic;
signal s_dma_read_data: dma_data_type;
signal s_dma_read_data_valid: std_logic;
signal s_dma_write_cmd_addr: dma_address_type;
signal s_dma_write_cmd_valid: std_logic;
signal s_dma_write_cmd_ready: std_logic;
signal s_dma_write_data: dma_data_type;
signal s_dma_write_data_ready: std_logic;
signal s_dma_write_finished: std_logic;
signal r_test_prefetch: std_logic;
signal r_test_raddr: std_logic_vector(9 downto 0);
signal r_test_waddr: std_logic_vector(9 downto 0);
signal s_test_dout: std_logic_vector(63 downto 0);
signal s_test_ren: std_logic;
begin
s_irq <= s_reg_control.test_irq;
led_o(7 downto 2) <= s_reg_control.test_led(7 downto 2);
-- Differential clock input for ADC clock.
inst_ibuf_adc_clk: IBUFDS
port map (
O => s_adc_clk_ibuf,
I => adc_clk_i(1),
IB => adc_clk_i(0)
);
-- Clock buffer for ADC clock.
inst_bufg_adc_clk: BUFG
port map (
I => s_adc_clk_ibuf,
O => clk_adc
);
inst_obuf_led0: OBUF
port map (
I => r_fclk_led,
O => led_o(0)
);
inst_obuf_led1: OBUF
port map (
I => r_adcclk_led,
O => led_o(1)
);
-- ARM/PS block design.
inst_blockdesign: entity work.puzzlefw_wrapper
port map (
sys_clk => clk_adc,
ps_fclk => clk_fclk,
peripheral_reset_0(0) => s_reset,
DDR_0_addr => DDR_0_addr,
DDR_0_ba => DDR_0_ba,
DDR_0_cas_n => DDR_0_cas_n,
DDR_0_ck_n => DDR_0_ck_n,
DDR_0_ck_p => DDR_0_ck_p,
DDR_0_cke => DDR_0_cke,
DDR_0_cs_n => DDR_0_cs_n,
DDR_0_dm => DDR_0_dm,
DDR_0_dq => DDR_0_dq,
DDR_0_dqs_n => DDR_0_dqs_n,
DDR_0_dqs_p => DDR_0_dqs_p,
DDR_0_odt => DDR_0_odt,
DDR_0_ras_n => DDR_0_ras_n,
DDR_0_reset_n => DDR_0_reset_n,
DDR_0_we_n => DDR_0_we_n,
FIXED_IO_0_ddr_vrn => FIXED_IO_0_ddr_vrn,
FIXED_IO_0_ddr_vrp => FIXED_IO_0_ddr_vrp,
FIXED_IO_0_mio => FIXED_IO_0_mio,
FIXED_IO_0_ps_clk => FIXED_IO_0_ps_clk,
FIXED_IO_0_ps_porb => FIXED_IO_0_ps_porb,
FIXED_IO_0_ps_srstb => FIXED_IO_0_ps_srstb,
APB_M_0_paddr => s_apb_paddr,
APB_M_0_penable => s_apb_penable,
APB_M_0_prdata => s_apb_prdata,
APB_M_0_pready(0) => s_apb_pready,
APB_M_0_psel(0) => s_apb_psel,
APB_M_0_pslverr(0) => s_apb_pslverr,
APB_M_0_pwdata => s_apb_pwdata,
APB_M_0_pwrite => s_apb_pwrite,
IRQ_F2P => s_irq,
S_AXI_HP0_0_araddr => s_axi_araddr,
S_AXI_HP0_0_arburst => s_axi_arburst,
S_AXI_HP0_0_arcache => s_axi_arcache,
S_AXI_HP0_0_arid => s_axi_arid,
S_AXI_HP0_0_arlen => s_axi_arlen,
S_AXI_HP0_0_arlock => s_axi_arlock,
S_AXI_HP0_0_arprot => s_axi_arprot,
S_AXI_HP0_0_arqos => s_axi_arqos,
S_AXI_HP0_0_arready => s_axi_arready,
S_AXI_HP0_0_arsize => s_axi_arsize,
S_AXI_HP0_0_arvalid => s_axi_arvalid,
S_AXI_HP0_0_awaddr => s_axi_awaddr,
S_AXI_HP0_0_awburst => s_axi_awburst,
S_AXI_HP0_0_awcache => s_axi_awcache,
S_AXI_HP0_0_awid => s_axi_awid,
S_AXI_HP0_0_awlen => s_axi_awlen,
S_AXI_HP0_0_awlock => s_axi_awlock,
S_AXI_HP0_0_awprot => s_axi_awprot,
S_AXI_HP0_0_awqos => s_axi_awqos,
S_AXI_HP0_0_awready => s_axi_awready,
S_AXI_HP0_0_awsize => s_axi_awsize,
S_AXI_HP0_0_awvalid => s_axi_awvalid,
S_AXI_HP0_0_bid => s_axi_bid,
S_AXI_HP0_0_bready => s_axi_bready,
S_AXI_HP0_0_bresp => s_axi_bresp,
S_AXI_HP0_0_bvalid => s_axi_bvalid,
S_AXI_HP0_0_rdata => s_axi_rdata,
S_AXI_HP0_0_rid => s_axi_rid,
S_AXI_HP0_0_rlast => s_axi_rlast,
S_AXI_HP0_0_rready => s_axi_rready,
S_AXI_HP0_0_rresp => s_axi_rresp,
S_AXI_HP0_0_rvalid => s_axi_rvalid,
S_AXI_HP0_0_wdata => s_axi_wdata,
S_AXI_HP0_0_wid => s_axi_wid,
S_AXI_HP0_0_wlast => s_axi_wlast,
S_AXI_HP0_0_wready => s_axi_wready,
S_AXI_HP0_0_wstrb => s_axi_wstrb,
S_AXI_HP0_0_wvalid => s_axi_wvalid
);
-- Memory-mapped registers.
inst_registers: entity work.registers
port map (
clk => clk_adc,
reset => s_reset,
apb_psel => s_apb_psel,
apb_penable => s_apb_penable,
apb_pwrite => s_apb_pwrite,
apb_paddr => s_apb_paddr,
apb_pwdata => s_apb_pwdata,
apb_pready => s_apb_pready,
apb_pslverr => s_apb_pslverr,
apb_prdata => s_apb_prdata,
reg_control => s_reg_control,
reg_status => s_reg_status,
reg_trigger => s_reg_trigger
);
-- AXI master.
inst_axi_master: entity work.dma_axi_master
generic map (
transfer_size => 16,
num_read_channels => 1,
num_write_channels => 1 )
port map (
clk => clk_adc,
reset => s_reset,
dma_en => s_reg_control.dma_en,
dma_busy => s_reg_status.dma_busy,
window_base_addr => s_reg_control.dma_buf_addr,
window_size => s_reg_control.dma_buf_size,
err_read => s_reg_status.dma_err_read,
err_write => s_reg_status.dma_err_write,
err_address => s_reg_status.dma_err_address,
err_any => s_reg_status.dma_err_any,
clear_errors => s_reg_trigger.dma_clear,
read_cmd_addr(0) => s_dma_read_cmd_addr,
read_cmd_valid(0) => s_dma_read_cmd_valid,
read_cmd_ready(0) => s_dma_read_cmd_ready,
read_data(0) => s_dma_read_data,
read_data_valid(0) => s_dma_read_data_valid,
write_cmd_addr(0) => s_dma_write_cmd_addr,
write_cmd_valid(0) => s_dma_write_cmd_valid,
write_cmd_ready(0) => s_dma_write_cmd_ready,
write_data(0) => s_dma_write_data,
write_data_ready(0) => s_dma_write_data_ready,
write_finished(0) => s_dma_write_finished,
m_axi_awid => s_axi_awid,
m_axi_awaddr => s_axi_awaddr,
m_axi_awlen => s_axi_awlen,
m_axi_awsize => s_axi_awsize,
m_axi_awburst => s_axi_awburst,
m_axi_awlock => s_axi_awlock,
m_axi_awcache => s_axi_awcache,
m_axi_awprot => s_axi_awprot,
m_axi_awqos => s_axi_awqos,
m_axi_awvalid => s_axi_awvalid,
m_axi_awready => s_axi_awready,
m_axi_wid => s_axi_wid,
m_axi_wdata => s_axi_wdata,
m_axi_wstrb => s_axi_wstrb,
m_axi_wlast => s_axi_wlast,
m_axi_wvalid => s_axi_wvalid,
m_axi_wready => s_axi_wready,
m_axi_bid => s_axi_bid,
m_axi_bresp => s_axi_bresp,
m_axi_bvalid => s_axi_bvalid,
m_axi_bready => s_axi_bready,
m_axi_arid => s_axi_arid,
m_axi_araddr => s_axi_araddr,
m_axi_arlen => s_axi_arlen,
m_axi_arsize => s_axi_arsize,
m_axi_arburst => s_axi_arburst,
m_axi_arlock => s_axi_arlock,
m_axi_arcache => s_axi_arcache,
m_axi_arprot => s_axi_arprot,
m_axi_arqos => s_axi_arqos,
m_axi_arvalid => s_axi_arvalid,
m_axi_arready => s_axi_arready,
m_axi_rid => s_axi_rid,
m_axi_rdata => s_axi_rdata,
m_axi_rresp => s_axi_rresp,
m_axi_rlast => s_axi_rlast,
m_axi_rvalid => s_axi_rvalid,
m_axi_rready => s_axi_rready
);
-- little test machine for DMA
inst_mem: xpm_memory_sdpram
generic map (
CLOCKING_MODE => "common_clock",
MEMORY_PRIMITIVE => "block",
MEMORY_SIZE => 1024 * 64,
ADDR_WIDTH_A => 10,
ADDR_WIDTH_B => 10,
WRITE_DATA_WIDTH_A => 64,
BYTE_WRITE_WIDTH_A => 64,
READ_DATA_WIDTH_B => 64,
READ_LATENCY_B => 1 )
port map (
clka => clk_adc,
clkb => clk_adc,
rstb => s_reset,
addra => r_test_waddr,
addrb => r_test_raddr,
dina => s_dma_read_data,
doutb => s_test_dout,
ena => s_dma_read_data_valid,
enb => s_test_ren,
wea => "1",
regceb => '1',
injectdbiterra => '0',
injectsbiterra => '0',
sleep => '0' );
s_dma_write_data <= not s_test_dout;
s_test_ren <= r_test_prefetch or s_dma_write_data_ready;
process (clk_adc) is
begin
if rising_edge(clk_adc) then
r_test_prefetch <= '0';
if s_dma_read_cmd_ready = '1' and s_dma_read_cmd_valid = '1' then
if unsigned(s_dma_read_cmd_addr) < 1008 then
s_dma_read_cmd_addr <= std_logic_vector(unsigned(s_dma_read_cmd_addr) + 16);
else
s_dma_read_cmd_valid <= '0';
end if;
end if;
if s_dma_read_data_valid = '1' then
s_reg_status.rcnt <= s_reg_status.rcnt + 1;
r_test_waddr <= std_logic_vector(unsigned(r_test_waddr) + 1);
if unsigned(r_test_waddr) = 1023 then
s_dma_write_cmd_valid <= '1';
r_test_prefetch <= '1';
end if;
end if;
if s_dma_write_cmd_ready = '1' and s_dma_write_cmd_valid = '1' then
if unsigned(s_dma_write_cmd_addr) < 1008 then
s_dma_write_cmd_addr <= std_logic_vector(unsigned(s_dma_write_cmd_addr) + 16);
else
s_dma_write_cmd_valid <= '0';
end if;
end if;
if (r_test_prefetch = '1') or (s_dma_write_data_ready = '1') then
r_test_raddr <= std_logic_vector(unsigned(r_test_raddr) + 1);
end if;
if s_dma_write_finished = '1' then
s_reg_status.wcnt <= s_reg_status.wcnt + 1;
end if;
if s_reg_trigger.start = '1' then
r_test_waddr <= (others => '0');
r_test_raddr <= (others => '0');
s_dma_read_cmd_addr <= (others => '0');
s_dma_write_cmd_addr <= (others => '0');
s_dma_read_cmd_valid <= '1';
s_dma_write_cmd_valid <= '0';
end if;
end if;
end process;
-- END test
process (clk_fclk) is
begin
if rising_edge(clk_fclk) then
r_fclk_cnt <= r_fclk_cnt + 1;
r_fclk_led <= r_fclk_cnt(r_fclk_cnt'high);
end if;
end process;
process (clk_adc) is
begin
if rising_edge(clk_adc) then
r_adcclk_cnt <= r_adcclk_cnt + 1;
r_adcclk_led <= r_adcclk_cnt(r_adcclk_cnt'high);
end if;
end process;
end architecture;

144
fpga/rtl/registers.vhd Normal file
View File

@ -0,0 +1,144 @@
--
-- Memory-mapped registers for Red Pitaya PuzzleFW firmware.
--
-- Joris van Rantwijk 2024
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.puzzlefw_pkg.all;
entity registers is
port (
-- Main clock, active on rising edge.
clk: in std_logic;
-- Reset, active high, synchronous to main clock.
reset: in std_logic;
-- APB bus interface.
apb_psel: in std_logic;
apb_penable: in std_logic;
apb_pwrite: in std_logic;
apb_paddr: in std_logic_vector(31 downto 0);
apb_pwdata: in std_logic_vector(31 downto 0);
apb_pready: out std_logic;
apb_pslverr: out std_logic;
apb_prdata: out std_logic_vector(31 downto 0);
-- FPGA interface.
reg_control: out registers_control;
reg_status: in registers_status;
reg_trigger: out registers_trigger
);
end entity;
architecture arch of registers is
type regs_type is record
pready: std_logic;
prdata: std_logic_vector(31 downto 0);
reg_control: registers_control;
reg_trigger: registers_trigger;
end record;
constant regs_init: regs_type := (
pready => '0',
prdata => (others => '0'),
reg_control => registers_control_init,
reg_trigger => registers_trigger_init
);
signal r: regs_type := regs_init;
signal rnext: regs_type;
begin
-- Drive output signals.
apb_pready <= r.pready;
apb_pslverr <= '0'; -- never signal errors
apb_prdata <= r.prdata;
reg_control <= r.reg_control;
reg_trigger <= r.reg_trigger;
-- Combinatorial process.
process (all) is
variable v: regs_type;
begin
-- Load current register values.
v := r;
-- Clear single-cycle trigger pulses.
v.reg_trigger := registers_trigger_init;
-- Respond to each APB access on the next clock cycle (no wait states).
v.pready := apb_psel and (not apb_penable);
-- Handle APB read transfer.
if apb_psel = '1' and apb_penable = '0' and apb_pwrite = '0' then
-- Initialize result to all-zero bits.
v.prdata := (others => '0');
-- Determine read result as function of address.
case to_integer(unsigned(apb_paddr and reg_addr_mask)) is
when reg_info => v.prdata := fw_info_word;
when reg_irq_enable => v.prdata(0) := r.reg_control.irq_enable;
when reg_dma_en => v.prdata(0) := r.reg_control.dma_en;
when reg_dma_status =>
v.prdata(0) := reg_status.dma_busy;
v.prdata(1) := reg_status.dma_err_read;
v.prdata(2) := reg_status.dma_err_write;
v.prdata(3) := reg_status.dma_err_address;
v.prdata(4) := reg_status.dma_err_any;
when reg_rcnt => v.prdata := std_logic_vector(reg_status.rcnt);
when reg_wcnt => v.prdata := std_logic_vector(reg_status.wcnt);
when reg_dma_buf_addr => v.prdata(31 downto 12) := r.reg_control.dma_buf_addr;
when reg_dma_buf_size => v.prdata(31 downto 12) := r.reg_control.dma_buf_size;
when reg_test_irq => v.prdata(7 downto 0) := r.reg_control.test_irq;
when reg_test_led => v.prdata(7 downto 0) := r.reg_control.test_led;
when others => null;
end case;
end if;
-- Handle APB write transfer.
if apb_psel = '1' and apb_penable = '0' and apb_pwrite = '1' then
case to_integer(unsigned(apb_paddr and reg_addr_mask)) is
when reg_irq_enable => v.reg_control.irq_enable := apb_pwdata(0);
when reg_dma_en => v.reg_control.dma_en := apb_pwdata(0);
when reg_dma_clear => v.reg_trigger.dma_clear := apb_pwdata(0);
when reg_start => v.reg_trigger.start := apb_pwdata(0);
when reg_dma_buf_addr => v.reg_control.dma_buf_addr := apb_pwdata(31 downto 12);
when reg_dma_buf_size => v.reg_control.dma_buf_size := apb_pwdata(31 downto 12);
when reg_test_irq => v.reg_control.test_irq := apb_pwdata(7 downto 0);
when reg_test_led => v.reg_control.test_led := apb_pwdata(7 downto 0);
when others => null;
end case;
end if;
-- Synchronous reset.
if reset = '1' then
v := regs_init;
end if;
-- Drive new register values to synchronous process.
rnext <= v;
end process;
-- Synchronous process.
process (clk) is
begin
if rising_edge(clk) then
r <= rnext;
end if;
end process;
end architecture;