mirror of
https://github.com/mist-devel/mist-board.git
synced 2026-01-30 05:04:17 +00:00
438 lines
13 KiB
Verilog
438 lines
13 KiB
Verilog
// ethernec.v
|
|
//
|
|
// Atari ST NE2000/ethernec implementation for the MiST board
|
|
// http://code.google.com/p/mist-board/
|
|
//
|
|
// Copyright (c) 2014 Till Harbaum <till@harbaum.org>
|
|
//
|
|
// This source file is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published
|
|
// by the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This source file is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
//
|
|
|
|
// TODO:
|
|
// rename rx_begin/tx_begin to rx_enable/tx_enable
|
|
// cpu writing curr??
|
|
// min framelen check here (not in io controller)
|
|
|
|
module ethernec (
|
|
// cpu register interface
|
|
input clk,
|
|
input [1:0] sel,
|
|
input [14:0] addr, // cpu word address!
|
|
output [15:0] dout,
|
|
|
|
// ethernet status word to be read by io controller
|
|
output [31:0] status,
|
|
|
|
// interface to allow the io controller to read frames from the tx buffer
|
|
input tx_begin, // rising edge before new tx byte stream is sent
|
|
input tx_strobe, // rising edge before each tx byte
|
|
output reg [7:0] tx_byte, // byte from transmit buffer
|
|
|
|
// interface to allow the io controller to write frames to the tx buffer
|
|
input rx_begin, // rising edge before new rx byte stream is sent
|
|
input rx_strobe, // rising edge before each rx byte
|
|
input [7:0] rx_byte, // byte to be written to rx buffer
|
|
|
|
// interface to allow mac address being set by io controller
|
|
input mac_begin, // rising edge before new mac is sent
|
|
input mac_strobe, // rising edge before each mac byte
|
|
input [7:0] mac_byte // mac address byte
|
|
);
|
|
|
|
// some non-zero and non-all-ones bytes as status flags
|
|
localparam STATUS_IDLE = 8'hfe;
|
|
localparam STATUS_TX_PENDING = 8'ha5;
|
|
localparam STATUS_TX_DONE = 8'h12;
|
|
|
|
reg [7:0] statusCode;
|
|
assign status = { statusCode, 5'h00, tbcr == tx_w_cnt, isr[1:0], tbcr };
|
|
|
|
// ----- bus interface signals as wired up on the ethernec/netusbee ------
|
|
// sel[0] = 0xfa0000 -> normal read
|
|
// sel[1] = 0xfb0000 -> write through address bus
|
|
wire ne_read = sel[0];
|
|
wire ne_write = sel[1];
|
|
wire [4:0] ne_addr = addr[12:8];
|
|
wire [7:0] ne_wdata = addr[7:0];
|
|
reg [7:0] ne_rdata;
|
|
assign dout = { ne_rdata, 8'h00 };
|
|
|
|
// ---------- ne2000 internal registers -------------
|
|
reg reset;
|
|
reg [7:0] cr; // ne command register
|
|
reg [7:0] isr; // ne interrupt service register
|
|
reg [7:0] imr; // interrupt mask register
|
|
reg [7:0] curr; // current page register
|
|
reg [7:0] bnry; // boundary page
|
|
reg [7:0] dcr;
|
|
reg [7:0] rcr; // receiver control register
|
|
reg [7:0] tcr; // transmitter control register
|
|
reg [7:0] tpsr;
|
|
reg [7:0] pstart; // rx buffer ring start page
|
|
reg [7:0] pstop; // rx buffer ring stop page
|
|
reg [7:0] par [5:0]; // 6 byte mac address register
|
|
reg [7:0] mar [7:0]; // 8 byte multicast hash register
|
|
reg [15:0] rbcr; // receiver byte count register
|
|
reg [15:0] rsar; // receiver address register
|
|
reg [15:0] tbcr; // transmitter byte count register
|
|
|
|
|
|
wire [1:0] ps = cr[7:6]; // register page select
|
|
|
|
// ------------- rx/tx buffers ------------
|
|
localparam FRAMESIZE = 1536;
|
|
|
|
reg [7:0] rx_buffer [FRAMESIZE+3:0]; // 1 ethernet frame + 4 bytes header
|
|
reg [15:0] rx_r_cnt, rx_w_cnt; // receive buffer byte counter
|
|
|
|
reg [7:0] tx_buffer [FRAMESIZE-1:0]; // 1 ethernet frame
|
|
reg [15:0] tx_w_cnt, tx_r_cnt; // transmit buffer byte counter
|
|
|
|
// ------------- io controller read access to tx buffer ------------
|
|
always @(posedge tx_strobe or negedge tx_begin) begin
|
|
if(!tx_begin)
|
|
tx_r_cnt <= 16'd0;
|
|
else begin
|
|
tx_byte <= tx_buffer[tx_r_cnt];
|
|
tx_r_cnt <= tx_r_cnt + 16'd1;
|
|
end
|
|
end
|
|
|
|
// whenver tx buffer has been read set tx irq
|
|
reg tx_doneD, tx_doneD2;
|
|
wire tx_done = tx_doneD && !tx_doneD2;
|
|
always @(posedge clk) begin
|
|
tx_doneD <= !tx_begin;
|
|
tx_doneD2 <= tx_doneD;
|
|
end
|
|
|
|
// ------------- set local mac address ------------
|
|
|
|
// mac address from io controller
|
|
reg [7:0] mac [5:0];
|
|
reg [2:0] mac_cnt;
|
|
|
|
always @(negedge mac_strobe or posedge mac_begin) begin
|
|
if(mac_begin)
|
|
mac_cnt <= 3'd0;
|
|
else begin
|
|
if(mac_cnt < 6) begin
|
|
mac[mac_cnt] <= mac_byte;
|
|
mac_cnt <= mac_cnt + 3'd1;
|
|
end
|
|
end
|
|
end
|
|
|
|
// cpu register read
|
|
always @(ne_read) begin
|
|
ne_rdata <= 8'd0;
|
|
if(ne_read) begin // $faxxxx
|
|
// cr, dma and reset are always available
|
|
if(ne_addr == 5'h00) ne_rdata <= cr;
|
|
|
|
// register page 0
|
|
if(ps == 2'd0) begin
|
|
if(ne_addr == 5'h04) ne_rdata <= 8'h23; // tsr: tx ok
|
|
if(ne_addr == 5'h07) ne_rdata <= isr;
|
|
end
|
|
|
|
// register page 1
|
|
if(ps == 2'd1) begin
|
|
if(ne_addr == 5'h07) ne_rdata <= curr;
|
|
end
|
|
|
|
// read dma register $10 - $17
|
|
if(ne_addr[4:3] == 2'b10)
|
|
ne_rdata <= rx_buffer[rx_r_cnt];
|
|
|
|
end
|
|
end
|
|
|
|
// delay internal reset signal
|
|
reg resetD, rx_beginD, rx_beginD2;
|
|
always @(posedge clk) begin
|
|
resetD <= reset;
|
|
rx_beginD <= rx_begin;
|
|
rx_beginD2 <= rx_beginD;
|
|
end
|
|
|
|
// generate an internal strobe signal to copy mac address and to setup header
|
|
wire int_strobe = ((rx_w_state == 2'd1)||(rx_w_state == 2'd3))?!clk:1'b0;
|
|
|
|
// internal mac transfer is started at the begin of the reset, internal header
|
|
// transfer is started at the end of the data transmission
|
|
wire int_begin = (reset && !resetD) || header_begin;
|
|
|
|
// 0=idle, 1=mac, 2=data, 3=header
|
|
reg [1:0] rx_w_state;
|
|
|
|
// Several sources can write into the rx_buffer. The user_io SPI client receiving
|
|
// data from the io controller or the ethernec core itself setting the mac address
|
|
// or adding the rx header
|
|
wire rx_write_clk = rx_strobe || int_strobe;
|
|
wire rx_write_begin = (rx_beginD && !rx_beginD2) || int_begin;
|
|
|
|
reg rx_lastByte;
|
|
|
|
// the ne2000 page size is 256 bytes. thus the page counters are increased
|
|
// every 256 bytes when a data transfer is in progress. First page is used when
|
|
// the first byte is written to 0x0004
|
|
wire rx_new_page = (rx_w_state == 2'd2) && ((rx_w_cnt[7:0] == 8'h00) || (rx_w_cnt == 14'h0004));
|
|
reg rx_new_pageD;
|
|
always @(negedge clk)
|
|
rx_new_pageD <= rx_new_page;
|
|
|
|
// -------- dummy page counter ---------
|
|
reg [7:0] rx_page_cnt;
|
|
always @(negedge clk) begin
|
|
if(rx_new_page && !rx_new_pageD)
|
|
rx_page_cnt <= rx_page_cnt + 8'd1;
|
|
end
|
|
|
|
// state/counter handling on one edge
|
|
always @(posedge rx_write_clk or posedge rx_write_begin) begin
|
|
if(rx_write_begin) begin
|
|
if(rx_w_state == 2'd1) begin
|
|
rx_w_cnt <= 16'd0; // mac is written to begin of buffer
|
|
end else if(rx_w_state == 2'd2) begin
|
|
// payload starts at byte 4 (after ne2000 header). Since the address advances
|
|
// right before the transfer we need to set the address to 3 before
|
|
rx_w_cnt <= 16'd3;
|
|
end if(rx_w_state == 2'd3) begin
|
|
rx_w_cnt <= 16'd0; // header is written to begin of buffer
|
|
end
|
|
|
|
end else begin
|
|
if(rx_w_state == 2'd1) begin
|
|
// after reset the mac is written to the first 6 bytes of the rx buffer
|
|
rx_w_cnt <= rx_w_cnt + 16'd1;
|
|
|
|
end else if(rx_w_state == 2'd2) begin
|
|
rx_w_cnt <= rx_w_cnt + 16'd1;
|
|
|
|
end else if(rx_w_state == 2'd3) begin
|
|
rx_w_cnt <= rx_w_cnt + 16'd1;
|
|
end
|
|
end
|
|
end
|
|
|
|
reg [15:0] rx_len; // number of bytes received from io controller
|
|
|
|
wire [7:0] header_byte =
|
|
(rx_w_cnt==0)?8'h01:
|
|
(rx_w_cnt==1)?curr:
|
|
(rx_w_cnt==2)?rx_len[7:0]:
|
|
(rx_w_cnt==3)?rx_len[15:8]:
|
|
8'h55;
|
|
|
|
always @(posedge clk) begin
|
|
rx_lastByte <= 1'b0;
|
|
|
|
if(((rx_w_state == 2'd1) && (rx_w_cnt == 3'd5)) ||
|
|
((rx_w_state == 2'd3) && (rx_w_cnt == 3'd3)))
|
|
rx_lastByte <= 1'b1;
|
|
end
|
|
|
|
// data transfer on other edge
|
|
always @(negedge rx_write_clk) begin
|
|
|
|
if(rx_w_state == 2'd1) begin
|
|
rx_buffer[rx_w_cnt] <= mac[rx_w_cnt];
|
|
|
|
end else if(rx_w_state == 2'd2) begin
|
|
// rx begin stays true over the entire transfer
|
|
// if(rx_begin)
|
|
rx_buffer[rx_w_cnt] <= rx_byte;
|
|
|
|
end else if(rx_w_state == 2'd3) begin
|
|
rx_buffer[rx_w_cnt] <= header_byte;
|
|
end
|
|
end
|
|
|
|
// register to delay receive counter increment by one cycle so this
|
|
// does happen after the read cycle has finished
|
|
reg rx_inc;
|
|
reg tx_inc;
|
|
|
|
// generate flag indicating that a header transfer is about to begin
|
|
reg header_begin;
|
|
always @(posedge clk) begin
|
|
header_begin <= 1'b0;
|
|
|
|
if(!rx_begin && rx_beginD)
|
|
header_begin <= 1'b1;
|
|
end
|
|
|
|
// write counter - header size (4) = number of bytes written
|
|
always @(negedge rx_begin)
|
|
rx_len <= rx_w_cnt - 16'd4;
|
|
|
|
// cpu write via read
|
|
always @(negedge clk) begin
|
|
|
|
// rising edge of new page signal causes current page counter to advance
|
|
// make sure counter stays within the limits of pstart/pstop
|
|
if(rx_new_page && !rx_new_pageD) begin
|
|
if(curr == (pstop-8'd1)) curr <= pstart;
|
|
else curr <= curr + 8'd1;
|
|
end
|
|
|
|
// last byte ends a mac or header transfer and causes the
|
|
// receiver state machine to return to the idle state
|
|
if(rx_lastByte) begin
|
|
rx_w_state <= 2'd0;
|
|
|
|
// trigger rx interrupt (PRX) at end of transfer
|
|
if(rx_w_state == 3)
|
|
isr[0] <= 1'b1;
|
|
end
|
|
|
|
// The rising edge of rx_begin indicates the start of a data transfer
|
|
if(rx_beginD && !rx_beginD2)
|
|
rx_w_state <= 2'd2;
|
|
|
|
// The falling edge of rx_begin marks the end of a data transfer.
|
|
// So we start setting up the pkt header after the end of the transfer
|
|
if(!rx_beginD && rx_beginD2)
|
|
rx_w_state <= 2'd3;
|
|
|
|
// cpu has read a byte from the rx buffer -> increase rx buffer read pointer
|
|
rx_inc <= 1'b0;
|
|
if(rx_inc && (rx_r_cnt < FRAMESIZE))
|
|
rx_r_cnt <= rx_r_cnt + 16'd1;
|
|
|
|
// cpu has written a byte to the tx buffer -> increase tx buffer write pointer
|
|
tx_inc <= 1'b0;
|
|
if(tx_inc && (tx_w_cnt < FRAMESIZE))
|
|
tx_w_cnt <= tx_w_cnt + 16'd1;
|
|
|
|
// signal end of transmission if tx buffer has been read by
|
|
// io controller
|
|
if(tx_done) begin
|
|
isr[1] <= 1'b1; // PTX
|
|
statusCode <= STATUS_TX_DONE;
|
|
end
|
|
|
|
// if cpu reads have internal side effects then ths is handled
|
|
// here (and not in the "register read" block above)
|
|
if(ne_read) begin
|
|
// register page 0
|
|
if(ps == 2'd0) begin
|
|
end
|
|
|
|
// register page 1
|
|
if(ps == 2'd1) begin
|
|
end
|
|
|
|
// read dma register $10-$17
|
|
if(ne_addr[4:3] == 2'b10)
|
|
rx_inc <= 1'b1;
|
|
|
|
// read reset register $18-$1f
|
|
if(ne_addr[4:3] == 2'b11) begin
|
|
reset <= 1'b1; // read to reset register sets reset
|
|
isr[7] <= 1'b1; // set reset flag in isr
|
|
|
|
statusCode <= STATUS_IDLE;
|
|
rx_w_state <= 2'd1; // mac address copy
|
|
end
|
|
end
|
|
|
|
if(ne_write) begin
|
|
if(ne_addr == 5'h00) begin
|
|
cr <= ne_wdata;
|
|
|
|
// writing the command register may actually start things ...
|
|
|
|
// check for remote read
|
|
if(ne_wdata[5:3] == 3'd1) begin
|
|
// this sets the receive counter, so data is being
|
|
// read from the position specified in rsar of the buffer
|
|
rx_r_cnt <= { 8'h00, rsar[7:0] };
|
|
end
|
|
|
|
// check for remote write
|
|
if(ne_wdata[5:3] == 3'd2) begin
|
|
// this resets the transmit counter, so data is being
|
|
// written to the beginning of the buffer
|
|
tx_w_cnt <= 16'h0000;
|
|
end
|
|
|
|
// check if TX bit was set
|
|
if(ne_wdata[2]) begin
|
|
// tx buffer is now full and its contents need to be sent to
|
|
// the io controller which in turn forwards it to its own nic
|
|
|
|
// number of bytes to be transmitted is in tbcr, tx_w_cnt should
|
|
// contain the same value since this is the number of write
|
|
// cycles performed on the tx buffer
|
|
statusCode <= STATUS_TX_PENDING;
|
|
|
|
// once the io controller has sent the packet bit 2 in the isr
|
|
// is being set. This will cause the ne2000 driver on atari side
|
|
// to start filling the tx buffer again
|
|
end
|
|
|
|
end
|
|
|
|
// register page 0
|
|
if(ps == 2'd0) begin
|
|
if(ne_addr == 5'h01) pstart <= ne_wdata;
|
|
if(ne_addr == 5'h02) pstop <= ne_wdata;
|
|
if(ne_addr == 5'h03) bnry <= ne_wdata;
|
|
if(ne_addr == 5'h04) tpsr <= ne_wdata;
|
|
if(ne_addr == 5'h05) tbcr[7:0] <= ne_wdata;
|
|
if(ne_addr == 5'h06) tbcr[15:8] <= ne_wdata;
|
|
if(ne_addr == 5'h07) isr <= isr & (~ne_wdata); // writing 1 clears bit
|
|
if(ne_addr == 5'h08) rsar[7:0] <= ne_wdata;
|
|
if(ne_addr == 5'h09) rsar[15:8] <= ne_wdata;
|
|
if(ne_addr == 5'h0a) rbcr[7:0] <= ne_wdata;
|
|
if(ne_addr == 5'h0b) rbcr[15:8] <= ne_wdata;
|
|
if(ne_addr == 5'h0c) rcr <= ne_wdata;
|
|
if(ne_addr == 5'h0d) tcr <= ne_wdata;
|
|
if(ne_addr == 5'h0e) dcr <= ne_wdata;
|
|
if(ne_addr == 5'h0f) imr <= ne_wdata;
|
|
end
|
|
|
|
// register page 1
|
|
if(ps == 2'd1) begin
|
|
if((ne_addr >= 5'h01) && (ne_addr < 5'h07))
|
|
par[ne_addr-5'd1] <= ne_wdata;
|
|
|
|
if(ne_addr == 5'h07) curr <= ne_wdata;
|
|
|
|
if((ne_addr >= 5'h08) && (ne_addr < 5'h10))
|
|
mar[ne_addr-5'd8] <= ne_wdata;
|
|
end
|
|
|
|
// write to dma register $10-$17
|
|
if(ne_addr[4:3] == 2'b10) begin
|
|
// prevent writing over end of buffer (whatever then happens ...)
|
|
if(tx_w_cnt < FRAMESIZE) begin
|
|
// store byte in buffer
|
|
tx_buffer[tx_w_cnt] <= ne_wdata;
|
|
|
|
// increase byte counter
|
|
tx_inc <= 1'b1;
|
|
end
|
|
end
|
|
|
|
// reset register $18-$1f
|
|
if(ne_addr[4:3] == 2'b11)
|
|
reset <= 1'b0; // write to reset register clears reset
|
|
|
|
end
|
|
end
|
|
|
|
endmodule |