Skip to content

Instantly share code, notes, and snippets.

@tomstorey
Last active March 12, 2024 06:38
Show Gist options
  • Save tomstorey/f3d79d536837f7eca634522d3546270b to your computer and use it in GitHub Desktop.
Save tomstorey/f3d79d536837f7eca634522d3546270b to your computer and use it in GitHub Desktop.
COMET68k_CPLD.sv
`timescale 1ns/1ns
/* These defines determine which machines are compiled in to the design */
`define INCLUDE_GATED_RESET
`define INCLUDE_BUS_WATCHDOG
`define INCLUDE_DRAM_MACHINE
`define INCLUDE_XBUS_MACHINE
`define INCLUDE_INTERRUPT_CONTROLLER
`define INCLUDE_BUS_ARBITER
`define INCLUDE_ETHERNET_MACHINE
/* Uncomment this define to source the CPU clock frequency from an external oscillator, rather than
* producing the CPU clock ourselves
*
* WARNING: External CPU clock currently results in instability, more work needed... */
//`define CPU_CLOCK_IS_EXTERNAL
/* Top module */
module COMET68k_CPLD(
/* Clocks */
input osc_40mhz,
output logic eth_clk,
`ifndef CPU_CLOCK_IS_EXTERNAL
output logic cpu_clk,
`else
input cpu_clk,
`endif
output logic timer_clk,
/* Global reset */
input n_reset,
/* Debug signals */
output logic debug1,
output logic debug2,
input cpld_func1,
/* Non-specific CPU signals */
input [23:16] addr,
input a1,
input a2,
input a3,
input n_as,
input n_uds,
input n_lds,
input n_write,
input n_dtack,
input [2:0] fc,
/* Non-specific bus signals */
output logic n_berr_drv,
/* DRAM machine */
output logic n_ras0,
output logic n_ras1,
output logic n_ucas,
output logic n_lcas,
output logic masel,
/* X-bus machine */
output logic xa0,
output logic n_xd_lreg_le,
output logic n_xd_lreg_oe,
output logic n_xd_ubuf_oe,
output logic n_xd_lbuf_oe,
output logic n_rom0_cs,
output logic n_rom1_cs,
output logic n_io_cs,
output logic n_uart_cs,
output logic n_timer_cs,
/* Expansion bus signals */
output logic own,
output logic ddir,
output logic n_dben,
output logic n_dtack_drv,
/* Ethernet machine */
output logic n_eth_cs,
inout n_eth_das,
inout n_eth_ready,
/* Interrupt controller signals */
input nmi,
input n_irq7,
input n_irq6,
input uart_irq,
input n_irq5,
input n_eth_irq,
input n_irq4,
input n_irq3,
input n_irq2,
input n_irq1,
input n_timer_irq,
input soft_irq, /* Software IRQ that isnt a trap */
input n_autovec,
output logic [2:0] n_ipl,
output logic n_vpa,
output logic n_iack_out,
/* Bus arbiter */
output logic n_br,
input n_bg,
input n_eth_br,
output logic n_eth_bg,
input n_br0,
output logic n_bg0,
input n_br1,
output logic n_bg1
);
/* Setup default pin states */
initial begin
`ifndef INCLUDE_DRAM_MACHINE
/* DRAM machine */
n_ras0 = 1'b1;
n_ras1 = 1'b1;
n_ucas = 1'b1;
n_lcas = 1'b1;
masel = 1'b0;
`endif
`ifndef INCLUDE_XBUS_MACHINE
/* X-bus machine */
xa0 = 1'b0;
n_xd_lreg_le = 1'b1;
n_xd_lreg_oe = 1'b1;
n_xd_ubuf_oe = 1'b1;
n_xd_lbuf_oe = 1'b1;
n_rom0_cs = 1'b1;
n_rom1_cs = 1'b1;
n_io_cs = 1'b1;
n_uart_cs = 1'b1;
n_timer_cs = 1'b1;
`endif
`ifndef INCLUDE_INTERRUPT_CONTROLLER
/* Interrupt controller */
n_ipl = 3'b111;
n_vpa = 1'b1;
n_iack_out = 1'b1;
`endif
`ifndef INCLUDE_BUS_ARBITER
/* Bus arbiter */
n_br = 1'b1;
n_eth_bg = 1'b1;
n_bg0 = 1'b1;
n_bg1 = 1'b1;
`endif
`ifndef INCLUDE_ETHERNET_MACHINE
/* Ethernet machine */
n_eth_cs = 1'b1;
n_eth_das = 1'bZ;
n_eth_ready = 1'bZ;
`endif
/* Expansion bus */
own = 1'b1;
ddir = 1'b1;
n_dben = 1'b1;
end
/* Gated reset driver */
`ifdef INCLUDE_GATED_RESET
wire n_gated_reset;
gated_reset gated_reset(
.n_reset(n_reset),
.n_gated_reset(n_gated_reset)
);
`else
wire n_gated_reset = 1'b1;
`endif /* INCLUDE_GATED_RESET */
/* Clock divider */
clock_divider #(
.CPU_CLK_TAP(0) /* 0 = 20MHz, 1 = 10MHz */
)
clock_divider(
.osc_40mhz(osc_40mhz),
.eth_clk(eth_clk),
`ifndef CPU_CLOCK_IS_EXTERNAL
.cpu_clk(cpu_clk),
`endif
.timer_clk(timer_clk)
);
/* Bus watchdog */
`ifdef INCLUDE_BUS_WATCHDOG
wire n_wd_berr;
bus_watchdog bus_watchdog(
.cpu_clk(cpu_clk),
.n_as(n_as),
.n_eth_bg(n_eth_bg),
.n_bg0(n_bg0),
.n_bg1(n_bg1),
.n_berr(n_wd_berr)
);
`else
wire n_wd_berr = 1'b1;
`endif /* INCLUDE_BUS_WATCHDOG */
/* X-bus machine */
`ifdef INCLUDE_XBUS_MACHINE
wire n_xb_dtack;
wire boot_ff;
wire xbus_decoded;
xbus_machine xbus_machine(
.clk(osc_40mhz),
.n_reset(n_reset),
.addr(addr),
.n_as(n_as),
.n_uds(n_uds),
.n_lds(n_lds),
.n_write(n_write),
.fc(fc),
.xa0(xa0),
.n_xd_lreg_le(n_xd_lreg_le),
.n_xd_lreg_oe(n_xd_lreg_oe),
.n_xd_ubuf_oe(n_xd_ubuf_oe),
.n_xd_lbuf_oe(n_xd_lbuf_oe),
.n_rom0_cs(n_rom0_cs),
.n_rom1_cs(n_rom1_cs),
.n_io_cs(n_io_cs),
.n_uart_cs(n_uart_cs),
.n_timer_cs(n_timer_cs),
.n_dtack(n_xb_dtack),
.boot_ff(boot_ff),
.decoded(xbus_decoded)
);
`else
wire n_xb_dtack = 1'b1;
wire boot_ff = 1'b0;
wire xbus_decoded = 1'b0;
`endif
/* DRAM machine */
`ifdef INCLUDE_DRAM_MACHINE
wire n_dram_dtack;
wire dram_decoded;
dram_machine dram_machine(
.clk(osc_40mhz),
.n_reset(n_gated_reset),
.boot_ff(boot_ff),
.addr(addr),
.n_as(n_as),
.n_uds(n_uds),
.n_lds(n_lds),
.fc(fc),
.n_ras0(n_ras0),
.n_ras1(n_ras1),
.n_ucas(n_ucas),
.n_lcas(n_lcas),
.masel(masel),
.n_dtack(n_dram_dtack),
.decoded(dram_decoded)
);
`else
wire n_dram_dtack = 1'b1;
wire dram_decoded = 1'b0;
`endif
/* Interrupt controller */
`ifdef INCLUDE_INTERRUPT_CONTROLLER
interrupt_controller interrupt_controller(
.clk(osc_40mhz),
.n_reset(n_reset),
.boot_ff(boot_ff),
.addr(addr),
.a3(a3),
.a2(a2),
.a1(a1),
.n_as(n_as),
.fc(fc),
.nmi(nmi),
.n_irq7(n_irq7),
.n_irq6(n_irq6),
.uart_irq(uart_irq),
.n_irq5(n_irq5),
.n_eth_irq(n_eth_irq),
.n_irq4(n_irq4),
.n_irq3(n_irq3),
.n_irq2(n_irq2),
.n_irq1(n_irq1),
.n_timer_irq(n_timer_irq),
.soft_irq(soft_irq),
.n_autovec(n_autovec),
.n_ipl(n_ipl),
.n_vpa(n_vpa),
.n_iack_out(n_iack_out)
);
`endif /* INCLUDE_INTERRUPT_CONTROLLER */
/* Bus arbiter */
`ifdef INCLUDE_BUS_ARBITER
bus_arbiter bus_arbiter(
.clk(osc_40mhz),
.n_reset(n_reset),
.n_as(n_as),
.n_dtack(n_dtack),
.n_br(n_br),
.n_bg(n_bg),
.n_eth_br(n_eth_br),
.n_eth_bg(n_eth_bg),
.n_br0(n_br0),
.n_bg0(n_bg0),
.n_br1(n_br1),
.n_bg1(n_bg1)
);
`endif /* INCLUDE_BUS_ARBITER */
/* Ethernet machine */
`ifdef INCLUDE_ETHERNET_MACHINE
wire n_eth_dtack;
wire eth_decoded;
ethernet_machine ethernet_machine(
.clk(osc_40mhz),
.n_reset(n_reset),
.addr(addr),
.n_as(n_as),
.n_uds(n_uds),
.n_lds(n_lds),
.n_eth_bg(n_eth_bg),
.n_dtack_in(n_dtack),
.n_eth_das(n_eth_das),
.n_eth_ready(n_eth_ready),
.n_eth_cs(n_eth_cs),
.n_dtack(n_eth_dtack),
.decoded(eth_decoded)
);
`else
wire n_eth_dtack = 1'b1;
wire eth_decoded = 1'b0;
`endif /* INCLUDE_ETHERNET_MACHINE */
wire onboard_access = !(!xbus_decoded && !dram_decoded && !eth_decoded);
always_comb begin
/* Composite signals */
n_dtack_drv = (n_xb_dtack && n_dram_dtack && n_eth_dtack);
n_berr_drv = n_wd_berr;
/* Expansion bus control signals
*
* own indicates whether an on-board device is currently the bus master. This is determined
* simply by both external bus grants being negated. When the bus is mastered by an on-board
* device, control signals are propagated outwards to the expansion bus. When the bus is
* mastered by an external device, control signals are propagated inwards from the expansion
* bus.
*
* ddir determines in which direction the data bus buffers will be configured for reads and
* writes. The direction is dependent on whether the bus is mastered by an on-board or
* external device. Using the nomenclature of the expansion bus buffers, A side pins are
* facing the on-board direction, and B side pins are facing the expansion bus.
*
* Master Operation Direction
* ------ --------- ---------
* On-board Read B->A
* On-board Write A->B
* External Read A->B
* External Write B->A
*
* n_dben enables the data bus buffers for read and write operations according to the
* following table.
*
* Master Accessing dben
* ------ --------- ----
* On-board On-board Disable
* On-board External Enable
* External External Disable
* External On-board Enable
*/
own = (n_bg0 && n_bg1);
ddir = n_write ^ own;
n_dben = !(n_write && !n_dtack && !onboard_access ||
!n_write);
/* Debug signals */
// debug1 = n_gated_reset;
// debug2 = 1'b0;
end
endmodule
/* Gated reset driver
*
* Provides a reset signal which is active only once for the first reset of the CPLD.
*/
module gated_reset(
input n_reset,
output logic n_gated_reset
);
reg gate;
initial begin
gate = 0;
end
always_ff @(posedge n_reset) begin
gate <= 1'b1;
end
always_comb begin
n_gated_reset = !(!n_reset && !gate);
end
endmodule /* gated_reset */
/* Clock Divider
*
* Divides the incomming 40MHz oscillator into several sub clocks:
*
* 1:2 division produces 20MHz for the ethernet controller
* 1:4 division produces 10MHz for the CPU
* 1:64 division produces 625KHz for the timers
*
* No reset is implemented, all of the sub clocks are produced continuously.
*/
module clock_divider
#(parameter BITS=6,
ETH_CLK_TAP=0,
CPU_CLK_TAP=1,
TIMER_CLK_TAP=5)
(
input osc_40mhz,
output logic eth_clk,
output logic cpu_clk,
output logic timer_clk
);
/* A n bit counter to act as the divider */
reg [BITS-1:0] divider;
/* Start the counter at 0 */
initial begin
divider = 0;
end
/* For every incoming clock edge, increment the counter */
always_ff @(negedge osc_40mhz) begin
divider <= divider + 1'b1;
end
/* Assign clock outputs */
always_comb begin
eth_clk = divider[ETH_CLK_TAP];
cpu_clk = divider[CPU_CLK_TAP];
timer_clk = divider[TIMER_CLK_TAP];
end
endmodule /* clock_divider */
`ifdef INCLUDE_BUS_WATCHDOG
/* Bus Watchdog
*
* The bus watchdog monitors the AS/ signal, and when ever it is asserted while the CPU is the bus
* master, a countdown timer is started. If that timer reaches zero while AS/ is still asserted, the
* watchdog will assert BERR/ to end the current bus cycle.
*
* The following bus cycle types are covered by this watchdog:
*
* Cycle type Normal termination method Timeout side effect
* ---------------------- ------------------------------------- --------------------------------
* Memory or peripheral DTACK/ asserted Bus error exception
* Vectored interrupt DTACK/ asserted by interruptor Spurious interrupt exception
* Autovectored interrupt VPA/ asserted by interrupt controller Spurious interrupt exception
* (internally asserted, or externally
* through n_autovec signal)
*/
module bus_watchdog
#(parameter BITS=5)
(
input cpu_clk,
input n_as,
input n_eth_bg,
input n_bg0,
input n_bg1,
output logic n_berr
);
/* A n bit counter to determine the timeout period */
reg [BITS-1:0] timeout;
/* Start timer at maximal value */
initial begin
timeout = -'d1;
end
/* We only care about generating bus errors when the CPU is the bus master. The CPU is the bus
* master when the bus grants to all other peripherals are negated. */
wire cpu_is_master = (n_eth_bg && n_bg0 && n_bg1);
/* The watchdog counter should be enabled when the CPU is the bus master, AS/ is asserted such
* that a bus cycle is active, and the counter is non-zero */
wire enable = cpu_is_master && !n_as && !(timeout == 'd0);
always_comb begin
/* Assert BERR/ whenever the timer reaches zero */
n_berr = !(timeout == 'd0);
end
always_ff @(posedge cpu_clk or posedge n_as) begin
if (n_as) begin
/* Effectively the reset state */
timeout <= -'d1;
end
else begin
if (enable) begin
/* Decrement the counter when enabled */
timeout <= timeout + -'d1;
end
end
end
endmodule /* bus_watchdog */
`endif /* INCLUDE_BUS_WATCHDOG */
`ifdef INCLUDE_XBUS_MACHINE
/* X-bus Machine
*
* The X-bus is an 8-bit bus within the computer that connects all of the smaller peripherals to
* the rest of the system. It also houses the ROMs.
*
* To permit all 8-bit devices to be readable and writeable at byte addresses, the X-bus machine
* implements the required handling of buffers and latches between the X-bus and the CPU bus along
* with a synthesised A0 address signal derived from the UDS.
*
* Through the latches and buffers it enables words to be read from a single ROM.
*
* ROM remapping is performed. After a system reset, ROM is accessible from address 0 until the
* CPU has read the initial SP and PC values. Once the CPU makes an access to the 0xFXXXXX address
* space, the ROMs are remapped to that address space and DRAM becomes available from address 0
* after boot_ff is set.
*
* The following address map is implemented in this machine:
*
* 0xCXXXXX On-board peripherals
* 0XXXX Debug display - always decoded and assumed to exist
* 1XXXX On-board IO (LEDs, configuration jumpers, etc)
* 2XXXX TL16C2552 dual UART
* 3XXXX DP8570A timer/RTC
* 0xFXXXXX ROMs
*
* Note: Ethernet chip select is decoded and handled through the ethernet machine. */
module xbus_machine
#(parameter ROM_WAIT_STATES=3)
(
input clk,
input n_reset,
input [23:16] addr,
input n_as,
input n_uds,
input n_lds,
input n_write,
input [2:0] fc,
output logic xa0,
output logic n_xd_lreg_le,
output logic n_xd_lreg_oe,
output logic n_xd_ubuf_oe,
output logic n_xd_lbuf_oe,
output logic n_rom0_cs,
output logic n_rom1_cs,
output logic n_debug_cs,
output logic n_io_cs,
output logic n_uart_cs,
output logic n_timer_cs,
output logic n_dtack,
output logic boot_ff,
output logic decoded
);
/* X-bus state machine states */
reg [2:0] m_state;
localparam
M_IDLE = 'd0,
M_ROM_LATCH_LOWER = 'd1,
M_PERIPH_CS_DELAY = 'd2,
M_WAIT_AS_NEGATE = 'd3,
M_RESET_BUS = 'd4;
/* Counter used throughout the machine to time various delays */
reg [2:0] delay;
/* Register defaults */
initial begin
m_state = M_IDLE;
delay = 'd0;
xa0 = 1'b1;
n_xd_lreg_le = 1'b1;
n_xd_lreg_oe = 1'b1;
n_xd_ubuf_oe = 1'b1;
n_xd_lbuf_oe = 1'b1;
boot_ff = 1'b0;
end
wire mem_cycle = (!n_as && (!n_uds || !n_lds) && (fc[2:0] != 3'b111));
wire rom_decoded_reset = (mem_cycle && !boot_ff && (addr[23:20] == 4'h0));
wire rom_decoded_booted = (mem_cycle && boot_ff && (addr[23:20] == 4'hF));
wire rom_decoded = (rom_decoded_reset || rom_decoded_booted);
wire debug_decoded = (mem_cycle && (addr[23:16] == 8'hC0));
wire io_decoded = (mem_cycle && (addr[23:16] == 8'hC1));
wire uart_decoded = (mem_cycle && (addr[23:16] == 8'hC2));
wire timer_decoded = (mem_cycle && (addr[23:16] == 8'hC3));
wire periph_decoded = (debug_decoded || io_decoded || uart_decoded || timer_decoded);
/* Assert chip selects and DTACK */
always_comb begin
/* X-bus has decoded something when ever its state machine isnt idle */
decoded = !(m_state == M_IDLE);
/* Assert chip selects */
n_rom0_cs = !(rom_decoded_booted && !addr[19]);
n_rom1_cs = !((rom_decoded_booted && addr[19]) || rom_decoded_reset);
n_debug_cs = !(debug_decoded && (m_state == M_WAIT_AS_NEGATE));
n_io_cs = !(io_decoded && (m_state == M_WAIT_AS_NEGATE));
n_uart_cs = !(uart_decoded && (m_state == M_WAIT_AS_NEGATE));
n_timer_cs = !(timer_decoded && (m_state == M_WAIT_AS_NEGATE));
/* Assert DTACK once we start waiting for AS to negate - assert DTACK for the debug display
* address range regardless of its presence in the system for more generic software */
n_dtack = !(!(n_rom0_cs && n_rom1_cs && n_debug_cs && n_io_cs && n_uart_cs && n_timer_cs) &&
(m_state == M_WAIT_AS_NEGATE));
end
always_ff @(negedge clk) begin
if (!n_reset) begin
/* Clear boot_ff any time the CPU is reset */
boot_ff <= 1'b0;
end
case (m_state)
M_IDLE:
begin
if (mem_cycle && !boot_ff && (addr[23:19] == 5'h1F)) begin
/* Set boot_ff on the first access to the address space of ROM1. This means
* that boot code must be stored in ROM1, which takes the upper most 512KB
* of the CPU address space.
*
* The first access to this space indicates that the CPU has begun executing
* code, and RAM can thus be enabled and become accessible from address 0.
*/
boot_ff <= 1'b1;
end
if (rom_decoded) begin
/* Setup for latching lower byte - whether or not a byte or word is being
* accessed, always load a word out of the ROM. The CPU will pick which bits
* it needs. */
xa0 <= 1'b1;
n_xd_lreg_le <= 1'b0;
n_xd_lreg_oe <= 1'b0;
/* ROM access delay before latching */
delay <= ROM_WAIT_STATES;
m_state <= M_ROM_LATCH_LOWER;
end
else if (periph_decoded) begin
/* Setup XA0 based on UDS */
xa0 <= n_uds;
if (n_write) begin
/* Reading IO - output to both the upper and lower halves of the data
* bus from the X-bus - the CPU will take what it needs */
n_xd_lreg_le <= 1'b0;
n_xd_lreg_oe <= 1'b0;
n_xd_ubuf_oe <= 1'b0;
end
else begin
/* Writing IO - based on xDS, enable only a single buffer towards
* the X-bus */
if (!n_uds) begin
n_xd_ubuf_oe <= 1'b0;
end
else begin
n_xd_lbuf_oe <= 1'b0;
end
end
/* Some X-bus peripherals, particularly the TL16C2552, seem to be sensitive
* to XA0 setup times. Therefore, peripheral chip selects are gated by the
* X-bus machine being in the M_WAIT_AS_NEGATE state. Make a short detour
* through M_PERIPH_CS_DELAY to allow some setup time for XA0. */
m_state <= M_PERIPH_CS_DELAY;
end
end
M_ROM_LATCH_LOWER:
if (delay == 'd0) begin
/* Latch lower byte now */
n_xd_lreg_le <= 1'b1;
/* Setup for buffering upper byte */
xa0 <= 1'b0;
n_xd_ubuf_oe <= 1'b0;
/* Now we just wait for AS to negate */
m_state <= M_WAIT_AS_NEGATE;
end
else begin
delay <= delay + -'d1;
end
M_PERIPH_CS_DELAY:
begin
/* Move straight to the next state */
m_state <= M_WAIT_AS_NEGATE;
end
M_WAIT_AS_NEGATE:
if (n_as) begin
m_state <= M_RESET_BUS;
end
M_RESET_BUS:
begin
/* Set defaults */
xa0 <= 1'b1;
n_xd_lreg_le <= 1'b1;
n_xd_lreg_oe <= 1'b1;
n_xd_ubuf_oe <= 1'b1;
n_xd_lbuf_oe <= 1'b1;
m_state <= M_IDLE;
end
endcase
end
endmodule /* xbus_machine */
`endif /* INCLUDE_XBUS_MACHINE */
`ifdef INCLUDE_DRAM_MACHINE
/* DRAM Machine
*
* Implements a state machine that refreshes the DRAM modules, as well as sequencing reads and
* writes.
*
* The DRAM machine decodes address 0-0x3FFFFF as long as boot_ff, which indicates that the CPU
* has begun executing code after fetching the ISP and IPC values, is set. Otherwise it lays
* dormant and does not perform any function other than refresh. */
module dram_machine
#(parameter REFRESH_CLOCKS=625,
REFRESH_WAIT_STATES=3,
ACCESS_RAS_WAIT_STATES=1,
ACCESS_CAS_WAIT_STATES=2,
PRECHARGE_WAIT_STATES=2)
(
input clk,
input n_reset,
input boot_ff,
input [23:16] addr,
input n_as,
input n_uds,
input n_lds,
input [2:0] fc,
output logic n_ras0,
output logic n_ras1,
output logic n_ucas,
output logic n_lcas,
output logic masel,
output logic n_dtack,
output logic decoded
);
/* DRAM state machine states */
reg [2:0] m_state;
localparam
M_IDLE = 'd0,
M_REFRESH_RAS = 'd1,
M_ACCESS_WAIT_XDS = 'd2,
M_ACCESS_CAS = 'd3,
M_PRECHARGE = 'd7;
/* DRAM refresh timer */
reg [9:0] dram_refresh_timer;
reg refresh_due;
/* Counter used throughout the machine to time various delays */
reg [1:0] delay;
/* por_delay inhibits refreshes until boot_ff is set. This satisfies the initial 200uS
* power-on-delay required by the DRAM modules prior to refresh cycles beginning. */
reg por_delay;
/* Register defaults */
initial begin
n_ras0 = 1'b1;
n_ras1 = 1'b1;
n_ucas = 1'b1;
n_lcas = 1'b1;
masel = 1'b0;
m_state = M_IDLE;
dram_refresh_timer = REFRESH_CLOCKS;
refresh_due = 1'b0;
delay = 'd0;
por_delay = 1'b0;
end
always_comb begin
/* DRAM is decoded when ... */
decoded = boot_ff && !n_as && (addr[23:22] == 2'b00) && (fc != 3'b111);
/* Assert DTACK/ when ever the machine is in the CAS portion of a cycle */
n_dtack = !(m_state == M_ACCESS_CAS && delay == 'd0);
end
always_ff @(negedge clk) begin
/* The DRAM modules used in COMET require a minimum of 200uS delay prior to starting refresh
* cycles, after which 8 refresh cycles are then required to achieve proper operation. Use
* boot_ff going high for the very first time as an initial power on delay. Software should
* then implement a delay loop to satisfy the initial 8 refresh cycles prior to making reads
* or writes. por_delay should not be reset thereafter to allow refreshes to continue, and
* thus maintain DRAM contents between CPU resets. */
if (!por_delay && boot_ff) begin
por_delay <= 1'b1;
end
/* Decrement the refresh timer. Set refresh_due flag once the timer reaches 0. */
if (dram_refresh_timer == 'd0) begin
refresh_due <= 1'b1;
dram_refresh_timer <= REFRESH_CLOCKS;
end
else begin
if (por_delay) begin
dram_refresh_timer <= dram_refresh_timer + -'d1;
end
end
case (m_state)
/* In the idle state, the machine is waiting for either a memory access cycle from an
* external device, or the refresh_due flag to be set to perform a refresh cycle. */
M_IDLE:
if (refresh_due) begin
/* Refresh cycle to be performed */
refresh_due <= 1'b0;
/* Assert CAS to both DRAM modules */
n_ucas <= 1'b0;
n_lcas <= 1'b0;
/* Move to assert RAS for refresh */
delay <= REFRESH_WAIT_STATES;
m_state <= M_REFRESH_RAS;
end
else if (decoded) begin
/* Memory access cycle - assert RAS according to DRAM bank */
if (addr[21] == 1'b0) begin
/* DRAM module 0 if A21 is low */
n_ras1 <= 1'b0;
end
else begin
/* DRAM module 1 if A21 is high */
n_ras0 <= 1'b0;
end
/* Move to wait for xDS strobes */
delay <= ACCESS_RAS_WAIT_STATES;
m_state <= M_ACCESS_WAIT_XDS;
end
/* DRAM refresh: assert RAS towards DRAM modules, and delay */
M_REFRESH_RAS:
if (delay == 'd0) begin
/* Refresh cycle is ending now. Deassert all signals. */
n_ras0 <= 1'b1;
n_ras1 <= 1'b1;
n_ucas <= 1'b1;
n_lcas <= 1'b1;
/* Move to precharge */
delay <= PRECHARGE_WAIT_STATES;
m_state <= M_PRECHARGE;
end
else begin
/* Assert RAS to both DRAM modules while delaying */
n_ras0 <= 1'b0;
n_ras1 <= 1'b0;
delay <= delay + -'d1;
end
/* Wait for xDS to be asserted before moving to CAS access delay */
M_ACCESS_WAIT_XDS:
if (delay == 'd0) begin
if (!n_uds || !n_lds) begin
/* Change to presenting column address */
masel <= 1'b1;
/* Move to CAS access delay - add one wait state to account for the fact that
* CAS's will be asserted in the next state */
delay <= ACCESS_CAS_WAIT_STATES + 'd1;
m_state <= M_ACCESS_CAS;
end
end
else begin
delay <= delay + -'d1;
end
/* CAS portion of memory access cycle */
M_ACCESS_CAS:
begin
/* Strobe CAS based on CPU data strobes - to guarantee setup times, CAS's are
* asserted here instead of ahead of time */
n_ucas <= n_uds;
n_lcas <= n_lds;
if (delay == 'd0) begin
/* Wait for AS to go idle before negating signals */
if (n_as) begin
masel <= 1'b0;
n_ras0 <= 1'b1;
n_ras1 <= 1'b1;
n_ucas <= 1'b1;
n_lcas <= 1'b1;
/* Move to precharge */
delay <= PRECHARGE_WAIT_STATES;
m_state <= M_PRECHARGE;
end
else if (!n_as && n_uds && n_lds) begin
/* Looks like we're in a RMW cycle. Proceed back to wait for xDS to be
* strobed again. */
m_state <= M_ACCESS_WAIT_XDS;
end
end
else begin
delay <= delay + -'d1;
end
end
/* Precharge delay in between cycles */
M_PRECHARGE:
if (delay == 'd0) begin
/* After precharge, move back to IDLE state to handle next cycle */
m_state <= M_IDLE;
end
else begin
delay <= delay + -'d1;
end
endcase
if (!n_reset) begin
m_state <= M_IDLE;
masel <= 1'b0;
n_ras0 <= 1'b1;
n_ras1 <= 1'b1;
n_ucas <= 1'b1;
n_lcas <= 1'b1;
end
end
endmodule /* dram_machine */
`endif /* INCLUDE_DRAM_MACHINE */
`ifdef INCLUDE_INTERRUPT_CONTROLLER
/* Interrupt Controller
*
* The interrupt controller prioritises and pends interrupt requests to the CPU.
*
* The following interrupt sources are prioritised in the following order:
*
* Highest: On-board NMI button at IRQ 7
* External IRQ 7
* External IRQ 6
* On-board UART at IRQ 5
* External IRQ 5
* On-board Ethernet controller at IRQ 4
* External IRQ 4
* External IRQ 3
* External IRQ 2
* External IRQ 1
* Lowest: On-board timer/RTC at IRQ 1
*
* The NMI button must be externally debounced.
*/
module interrupt_controller(
input clk,
input n_reset,
input boot_ff,
input [23:16] addr,
input a3,
input a2,
input a1,
input n_as,
input [2:0] fc,
input nmi,
input n_irq7,
input n_irq6,
input uart_irq,
input n_irq5,
input n_eth_irq,
input n_irq4,
input n_irq3,
input n_irq2,
input n_irq1,
input n_timer_irq,
input soft_irq,
input n_autovec,
output logic [2:0] n_ipl,
output logic n_vpa,
output logic n_iack_out
);
/* NMI button state machine */
reg [1:0] m_state;
localparam
M_IDLE = 'd0,
M_ACKNOWLEDGED = 'd1,
M_EXTERNAL = 'd2;
/* A register that determines if the NMI input has been acknowledged as asserted, and causes it
* to be masked until it is negated */
reg nmi_acked;
/* Register defaults */
initial begin
m_state = M_IDLE;
nmi_acked = 1'b1;
n_vpa = 1'b1;
n_iack_out = 1'b1;
end
wire iack = (!n_as && (addr[19:16] == 4'b1111) && (fc[2:0] == 3'b111));
wire level7 = ( a3 && a2 && a1);
wire level6 = ( a3 && a2 && !a1);
wire level5 = ( a3 && !a2 && a1);
wire level4 = ( a3 && !a2 && !a1);
wire level3 = (!a3 && a2 && a1);
wire level2 = (!a3 && a2 && !a1);
wire level1 = (!a3 && !a2 && a1);
wire nmi_ack = (nmi && !nmi_acked && level7);
wire uart_ack = (uart_irq && level5);
wire eth_ack = (!n_eth_irq && level4);
wire timer_ack = (!n_timer_irq && n_irq1 && level1);
wire soft_irq_ack = (soft_irq && n_timer_irq && n_irq1 && level1);
wire ext_irq7 = (!nmi && level7);
wire ext_irq5 = (!uart_irq && level5);
wire ext_irq4 = (n_eth_irq && level4);
wire onboard_irq = (nmi_ack || uart_ack || eth_ack || timer_ack || soft_irq_ack);
wire external_irq = (ext_irq7 || level6 || ext_irq5 || ext_irq4 || level3 || level2 || level1);
/* Priority encoder for IPLx */
always_comb begin
/* Default to no pending interrupt */
n_ipl = ~3'd0;
if (boot_ff) begin
/* Interrupts other than NMI are only pended to the CPU once it has fetched the reset
* vector and begun executing code from its run-time address map */
if (!n_irq1 || !n_timer_irq || soft_irq) begin
/* IPL 1: external IRQ 1, on-board timer/RTC or software IRQ */
n_ipl = ~3'd1;
end
if (!n_irq2) begin
/* IPL 2: external IRQ 2 */
n_ipl = ~3'd2;
end
if (!n_irq3) begin
/* IPL 3: external IRQ 3 */
n_ipl = ~3'd3;
end
if (!n_eth_irq || !n_irq4) begin
/* IPL 4: on-board Ethernet controller or external IRQ 4 */
n_ipl = ~3'd4;
end
if (uart_irq || !n_irq5) begin
/* IPL 5: on-board UART or external IRQ 5 */
n_ipl = ~3'd5;
end
if (!n_irq6) begin
/* IPL 6: external IRQ 6 */
n_ipl = ~3'd6;
end
end
if ((nmi && !nmi_acked) || !n_irq7) begin
/* IPL 7: on-board NMI button or external IRQ 7 */
n_ipl = ~3'd7;
end
end
/* NMI button machine */
always_ff @(negedge clk) begin
if (!n_reset) begin
/* Resetting */
m_state <= M_IDLE;
nmi_acked <= 1'b1;
n_vpa <= 1'b1;
n_iack_out <= 1'b1;
end
else begin
/* If NMI is ack'd and the NMI signal becomes negated, clear the nmi_ack flag */
if (nmi_acked && !nmi) begin
nmi_acked <= 1'b0;
end
case (m_state)
M_IDLE:
begin
if (iack) begin
if (onboard_irq) begin
/* Autovector all on-board interrupt sources */
n_vpa <= 1'b0;
/* If NMI is asserted, set the nmi_ack flag to mask NMI until it is
* negated */
if (nmi_ack) begin
nmi_acked <= 1'b1;
end
m_state <= M_ACKNOWLEDGED;
end
else begin
/* Assert IACK out to allow an external interruptor to either
* generate a vectored or autovectored interrupt */
n_iack_out <= 1'b0;
m_state <= M_EXTERNAL;
end
end
end
/* Wait for the IACK cycle to end */
M_ACKNOWLEDGED:
if (n_as) begin
n_vpa <= 1'b1;
m_state <= M_IDLE;
end
/* Wait for the IACK cycle to end. During this state, relay the n_autovec signal to
* VPA to allow external interruptors to autovector if required. Alternatively they
* may vector an interrupt. */
M_EXTERNAL:
if (n_as) begin
n_vpa <= 1'b1;
n_iack_out <= 1'b1;
m_state <= M_IDLE;
end
else begin
n_vpa <= n_autovec;
end
endcase
end
end
endmodule /* interrupt_controller */
`endif /* INCLUDE_INTERRUPT_CONTROLLER */
`ifdef INCLUDE_BUS_ARBITER
/* Bus Arbiter
*
* Manages access to the system busses, prioritising requests between the following sources:
*
* Highest: On-board Ethernet controller
* External request level 0
* Lowest: External request level 1
*/
module bus_arbiter(
input clk,
input n_reset,
input n_as,
input n_dtack,
output logic n_br,
input n_bg,
input n_eth_br,
output logic n_eth_bg,
input n_br0,
output logic n_bg0,
input n_br1,
output logic n_bg1
);
/* Bus arbiter state machine */
reg [2:0] m_state;
localparam
M_IDLE = 'd0,
M_WAIT_CPU_ASSERT_GRANT = 'd1,
M_GRANT_TO_ETH = 'd2,
M_GRANT_TO_BR0 = 'd3,
M_GRANT_TO_BR1 = 'd4,
M_WAIT_CPU_NEGATE_GRANT = 'd5;
/* Register defaults */
initial begin
m_state = M_IDLE;
end
always_comb begin
/* BR is asserted once moving out of the idle state, and negated once the requestor has
* negated its request and we are waiting for the CPU to re-acquire the bus */
n_br = (m_state == M_IDLE) || (m_state == M_WAIT_CPU_NEGATE_GRANT);
/* Assert bus grants */
n_eth_bg = !(m_state == M_GRANT_TO_ETH);
n_bg0 = !(m_state == M_GRANT_TO_BR0);
n_bg1 = !(m_state == M_GRANT_TO_BR1);
end
always_ff @(negedge clk) begin
if (!n_reset) begin
/* Resetting */
m_state <= M_IDLE;
end
else begin
case (m_state)
/* The idle state waits for a request to be received from either the on-board
* Ethernet controller, or one of the two external request signals */
M_IDLE:
if (!n_eth_br || !n_br0 || !n_br1) begin
m_state <= M_WAIT_CPU_ASSERT_GRANT;
end
/* Wait for the CPU to grant access to the bus and for the current bus cycle to
* complete, then move to grant access to the higest priority requester */
M_WAIT_CPU_ASSERT_GRANT:
if (!n_bg && n_as && n_dtack) begin
if (!n_eth_br) begin
m_state <= M_GRANT_TO_ETH;
end
else if (n_eth_br && !n_br0) begin
m_state <= M_GRANT_TO_BR0;
end
else if (n_eth_br && n_br0 && !n_br1) begin
m_state <= M_GRANT_TO_BR1;
end
else begin
m_state <= M_WAIT_CPU_NEGATE_GRANT;
end
end
/* Bus granted to on-board ethernet
*
* Once the ethernet controller gives up the bus, proceed to wait for the CPU to
* re-acquire the bus */
M_GRANT_TO_ETH:
if (n_eth_br) begin
m_state <= M_WAIT_CPU_NEGATE_GRANT;
end
/* Bus granted to external priority 0 */
M_GRANT_TO_BR0:
if (n_br0) begin
m_state <= M_WAIT_CPU_NEGATE_GRANT;
end
/* Bus granted to external priority 0 */
M_GRANT_TO_BR1:
if (n_br0) begin
m_state <= M_WAIT_CPU_NEGATE_GRANT;
end
/* Wait for the CPU to negate bus grant and for the current bus cycle to complete */
M_WAIT_CPU_NEGATE_GRANT:
if (n_bg && n_as && n_dtack) begin
m_state <= M_IDLE;
end
endcase
end
end
endmodule /* bus_arbiter */
`endif /* INCLUDE_BUS_ARBITER */
`ifdef INCLUDE_ETHERNET_MACHINE
/* Ethernet Machine
*
* The Ethernet machine is responsible for decoding accesses to the Ethernet controller, as well as
* sequencing accesses to and from it through the use of the DAS and READY signals.
*
* The Ethernet controller is decoded in the address space 0xC4XXXX.
*/
module ethernet_machine(
input clk,
input n_reset,
input [23:16] addr,
input n_as,
input n_uds,
input n_lds,
input n_eth_bg,
input n_dtack_in,
inout n_eth_das,
inout n_eth_ready,
output logic n_eth_cs,
output logic n_dtack,
output logic decoded
);
/* Ethernet machine state machine */
reg [1:0] m_state;
localparam
M_IDLE = 'd0,
M_SLAVE_CYCLE = 'd1,
M_MASTER_CYCLE = 'd2;
/* Register defaults */
initial begin
m_state = M_IDLE;
end
always_comb begin
/* Access to the Ethernet controller is by word only */
decoded = (n_eth_bg && !n_as && !n_uds && !n_lds && (addr[23:16] == 8'hC4));
/* Chipselect */
n_eth_cs = !decoded;
/* DTACK/ is only driven during a slave cycle, and is essentially just a relayed copy of
* the Ethernet controllers READY/ signal */
n_dtack = !((m_state == M_SLAVE_CYCLE) && !n_eth_ready);
/* Assert DAS to the Ethernet controller when we are in a slave cycle */
n_eth_das = (m_state == M_SLAVE_CYCLE) ? 1'b0 : 1'bZ;
/* Assert READY towards the Ethernet controller during master cycles when DTACK has been
* asserted by some other device in the system */
n_eth_ready = ((m_state == M_MASTER_CYCLE) && !n_dtack_in) ? 1'b0 : 1'bZ;
end
always_ff @(negedge clk) begin
if (!n_reset) begin
/* Resetting */
m_state <= M_IDLE;
end
else begin
case (m_state)
M_IDLE:
if (decoded) begin
m_state <= M_SLAVE_CYCLE;
end
else if (!n_eth_bg && !n_eth_das) begin
m_state <= M_MASTER_CYCLE;
end
/* In this state we are waiting for the Ethernet controller to assert its READY/
* signal, which indicates that it has accepted data during a write or presented
* data during a read. This is then relayed to the CPU as DTACK/, and we then wait
* for AS/ to go high to indicate the bus cycle is complete. */
M_SLAVE_CYCLE:
if (n_as) begin
m_state <= M_IDLE;
end
/* When the Ethernet controller is a master, we only need relay the state of DTACK/
* to it so that it knows when it can end its own bus cycles. This is done by
* mirroring our DTACK/ input to the n_eth_ready signal once it is asserted, and
* until AS/ is negated. */
M_MASTER_CYCLE:
if (n_as) begin
m_state <= M_IDLE;
end
endcase
end
end
endmodule /* ethernet_machine */
`endif /* INCLUDE_ETHERNET_MACHINE */
/* END */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment