1
0
mirror of https://github.com/mist-devel/mist-board.git synced 2026-02-06 16:14:42 +00:00
Files
mist-devel.mist-board/cores/mist/blitter.v
2015-01-30 15:13:41 +00:00

579 lines
16 KiB
Verilog

//
// blitter.v
//
// Atari ST BLiTTER implementation for the MiST board
// http://code.google.com/p/mist-board/
//
// Copyright (c) 2013 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/>.
// blitter docs:
// http://mikro.naprvyraz.sk/docs/ST_E/BLITTER.TXT
// http://paradox.atari.org/files/BLIT_FAQ.TXT
module blitter (
input [1:0] bus_cycle,
// cpu register interface
input clk,
input reset,
input sel,
input [4:0] addr,
input [15:0] din,
output reg [15:0] dout,
input uds,
input lds,
input rw,
// bus master interface
output [23:1] bm_addr,
output reg bm_write,
output reg bm_read,
output [15:0] bm_data_out,
input [15:0] bm_data_in,
input br_in,
output reg br_out,
output irq,
input bg,
input turbo // 16Mhz blitter
);
assign irq = busy;
// CPU controlled register set
reg [15:0] halftone_ram[15:0];
reg [15:1] src_x_inc;
reg [15:1] src_y_inc;
reg [23:1] src_addr;
reg [15:0] endmask1;
reg [15:0] endmask2;
reg [15:0] endmask3;
reg [15:1] dst_x_inc;
reg [15:1] dst_y_inc;
reg [23:1] dst_addr;
reg [15:0] x_count;
reg [15:0] x_count_latch;
reg [15:0] y_count;
reg [1:0] hop;
reg [3:0] op;
reg [3:0] line_number;
reg [3:0] line_number_latch;
reg smudge;
reg hog;
reg busy;
reg [3:0] skew;
reg nfsr;
reg fxsr;
// specify which bus cycles to use
wire cycle_advance = (bus_cycle == 2'd0) || (turbo && (bus_cycle == 2'd2));
wire cycle_read = (bus_cycle == 2'd1) || (turbo && (bus_cycle == 2'd3));
// latch bus cycle information to use at the end of the cycle (posedge clk)
reg cycle_advanceL, cycle_readL;
always @(negedge clk) begin
cycle_advanceL <= cycle_advance;
cycle_readL <= cycle_read;
end
// ------------------ cpu interface --------------------
// CPU READ
always @(sel, rw, addr, src_y_inc, src_x_inc, src_addr, endmask1, endmask2, endmask3,
dst_x_inc, dst_y_inc, dst_addr, x_count, y_count, hop, op, busy, hog,
smudge, line_number_latch, fxsr, nfsr, skew) begin
dout = 16'h0000;
if(sel && rw) begin
if((addr >= 5'h00) && (addr <= 5'h0f)) dout <= halftone_ram[addr];
if(addr == 5'h10) dout <= { src_x_inc, 1'b0 };
if(addr == 5'h11) dout <= { src_y_inc, 1'b0 };
if(addr == 5'h12) dout <= { 8'h00, src_addr[23:16] };
if(addr == 5'h13) dout <= { src_addr [15:1], 1'b0 };
if(addr == 5'h14) dout <= endmask1;
if(addr == 5'h15) dout <= endmask2;
if(addr == 5'h16) dout <= endmask3;
if(addr == 5'h17) dout <= { dst_x_inc, 1'b0 };
if(addr == 5'h18) dout <= { dst_y_inc, 1'b0 };
if(addr == 5'h19) dout <= { 8'h00, dst_addr[23:16] };
if(addr == 5'h1a) dout <= { dst_addr [15:1], 1'b0 };
if(addr == 5'h1b) dout <= x_count;
if(addr == 5'h1c) dout <= y_count;
// since reading them has no side effect we can return the 8 bit registers
// without caring for uds/lds
if(addr == 5'h1d) dout <= { 6'b000000, hop, 4'b0000, op };
if(addr == 5'h1e) dout <= { busy, hog, smudge, 1'b0, line_number_latch, fxsr, nfsr, 2'b00, skew };
end
end
// flag to initialze state machine
reg init;
// wait 1 bus cycle after bus has been requested to avoid that counters are updated before
// first bus transfer has taken place
reg wait4bus;
// counter for cooperative (non-hog) bus access
reg [5:0] bus_coop_cnt;
reg bus_owned;
// the state machine runs through most states for every word it processes
// state 0: normal source read cycle
// state 1: destination read cycle
// state 2: destination write cycle
// state 3: extra source read cycle (fxsr)
reg [1:0] state;
// latch for read data
reg [15:0] bm_data_in_latch;
// latch incoming data at end of bus cycle
always @(posedge clk)
if(cycle_readL)
bm_data_in_latch <= bm_data_in;
always @(negedge clk) begin
// ---------- blitter cpu register write interfce ............
if(reset) begin
busy <= 1'b0;
state <= 2'd0;
wait4bus <= 1'b0;
end else begin
if(sel && ~rw) begin
// ------ 16/32 bit registers, not byte adressable ----------
if((addr >= 5'h00) && (addr <= 5'h0f)) halftone_ram[addr] <= din;
if(addr == 5'h10) src_x_inc <= din[15:1];
if(addr == 5'h11) src_y_inc <= din[15:1];
if(addr == 5'h12) src_addr[23:16] <= din[7:0];
if(addr == 5'h13) src_addr[15:1] <= din[15:1];
if(addr == 5'h14) endmask1 <= din;
if(addr == 5'h15) endmask2 <= din;
if(addr == 5'h16) endmask3 <= din;
if(addr == 5'h17) dst_x_inc <= din[15:1];
if(addr == 5'h18) dst_y_inc <= din[15:1];
if(addr == 5'h19) dst_addr[23:16] <= din[7:0];
if(addr == 5'h1a) dst_addr[15:1] <= din[15:1];
if(addr == 5'h1b) begin
x_count <= din;
x_count_latch <= din; // x_count is latched to be reloaded at each end of line
end
if(addr == 5'h1c) y_count <= din;
// ------ 8 bit registers ----------
// uds -> even bytes via d15:d8
// lds -> odd bytes via d7:d0
if((addr == 5'h1d) && ~uds) hop <= din[9:8];
if((addr == 5'h1d) && ~lds) op <= din[3:0];
if((addr == 5'h1e) && ~uds) begin
line_number_latch <= din[11:8];
smudge <= din[13];
hog <= din[14];
// writing busy with 1 starts the blitter, but only if y_count != 0
if(din[15] && (y_count != 0)) begin
busy <= 1'b1;
wait4bus <= 1'b1;
bus_coop_cnt <= 6'd0;
// initialize only if blitter is newly being started and not
// if it's already running
if(!busy) init <= 1'b1;
// make sure the predicted x_count is one steap ahead of the
// real x_count
if(x_count != 1) x_count_next <= x_count - 1'd1;
else x_count_next <= x_count_latch;
end
end
if((addr == 5'h1e) && ~lds) begin
skew <= din[3:0];
nfsr <= din[6];
fxsr <= din[7];
end
end
end
// ----------------------------------------------------------------------------------
// -------------------------- blitter state machine ---------------------------------
// ----------------------------------------------------------------------------------
// entire state machine advances in bus_cycle 0
// (the cycle before the one being used by the cpu/blitter for memory access)
if(cycle_advance) begin
// grab bus if blitter is supposed to run (busy == 1) and we're not waiting for the bus
br_out <= busy && (!wait4bus || (wait4bus && (bus_coop_cnt == 0)));
bus_owned <= busy && !wait4bus;
// clear busy flag if blitter is done
if(y_count == 0) busy <= 1'b0;
// the bus is freed/grabbed once this counter runs down to 0 in non-hog mode
if(busy && !hog && !br_in && (bus_coop_cnt != 0))
bus_coop_cnt <= bus_coop_cnt - 6'd1;
// change between both states (bus grabbed and bus released)
if(bus_coop_cnt == 0) begin
// release bus immediately, grab bus only if bg is set
if(!wait4bus || (wait4bus && bg)) begin
bus_coop_cnt <= 6'd63;
wait4bus <= !wait4bus;
end
end
// blitter has just been setup, so init the state machine in first step
if(init) begin
init <= 1'b0;
line_number <= line_number_latch;
if(skip_src_read) begin // skip source read (state 0)
if(dest_required) state <= 2'd1; // but dest needs to be read
else state <= 2'd2; // also dest needs to be read
end else if(fxsr) state <= 2'd3; // first extra source read
else state <= 2'd0; // normal source read
end
// advance state machine only if bus is owned
if(bus_owned && !br_in && (y_count != 0)) begin
// first extra source read (fxsr)
if(state == 2'd3) begin
if(src_x_inc[15] == 1'b0) src <= { src[15:0], bm_data_in_latch};
else src <= { bm_data_in_latch, src[31:16]};
src_addr <= src_addr + { {8{src_x_inc[15]}}, src_x_inc };
state <= 2'd0;
end
if(state == 3'd0) begin
// don't do the read of the last word in a row if nfsr is set
if(nfsr && last_word_in_row) begin
// no final source read, but shifting anyway
if(src_x_inc[15] == 1'b0) src[31:16] <= src[15:0];
else src[15:0] <= src[31:16];
src_addr <= src_addr + { {8{src_y_inc[15]}}, src_y_inc } - { {8{src_x_inc[15]}}, src_x_inc };
end else begin
if(src_x_inc[15] == 1'b0) src <= { src[15:0], bm_data_in_latch};
else src <= { bm_data_in_latch, src[31:16]};
if(x_count != 1) // do signed add by sign expanding XXX_x_inc
src_addr <= src_addr + { {8{src_x_inc[15]}}, src_x_inc };
else // we are at the end of a line
src_addr <= src_addr + { {8{src_y_inc[15]}}, src_y_inc };
end
// jump directly to destination write if no destination read is required
if(dest_required) state <= 2'd1;
else state <= 2'd2;
end
if(state == 2'd1) begin
dest <= bm_data_in_latch;
state <= 2'd2;
end
if(state == 2'd2) begin
// y_count != 0 means blitter is (still) active
if(y_count != 0) begin
if(x_count != 1) begin
// we are at the begin or within a line (have not reached the end yet)
// do signed add by sign expanding XXX_x_inc
dst_addr <= dst_addr + { {8{dst_x_inc[15]}}, dst_x_inc };
x_count <= x_count - 8'd1;
end else begin
// we are at the end of a line but not finished yet
// do signed add by sign expanding XXX_y_inc
dst_addr <= dst_addr + { {8{dst_y_inc[15]}}, dst_y_inc };
if(!dst_y_inc[15]) line_number <= line_number + 4'd1;
else line_number <= line_number - 4'd1;
x_count <= x_count_latch;
y_count <= y_count - 8'd1;
end
// also advance the predicted next x_count
if(x_count_next != 1) x_count_next <= x_count_next - 1'd1;
else x_count_next <= x_count_latch;
end
if(skip_src_read) begin // skip source read (state 0)
if(next_dest_required) state <= 2'd1; // but dest needs to be read
else state <= 2'd2; // also dest needs to be read
end
else if(last_word_in_row && fxsr) state <= 2'd3; // extra state 3
else state <= 2'd0; // normal source read state
end
end
end
end
// source read takes place in state 0 (normal source read) and 3 (fxsr)
assign bm_addr = ((state == 2'd0)||(state == 2'd3))?src_addr:dst_addr;
// ----------------- blitter busmaster engine -------------------
always @(posedge clk) begin
bm_read <= 1'b0;
bm_write <= 1'b0;
if(bus_owned && !br_in && (y_count != 0) && cycle_advanceL) begin
if(state == 2'd0) bm_read <= 1'b1;
else if(state == 2'd1) bm_read <= 1'b1;
else if(state == 2'd2) bm_write <= 1'b1;
else if(state == 2'd3) bm_read <= 1'b1; // fxsr state
end
end
// internal registers
// predicts what the next x_count will be. Is needed to determine the first state in the nect
// blitter cycle e.g. to know whether we can skip the source/dest read of the next cycle
reg [15:0] x_count_next;
reg [31:0] src; // 32 bit source read buffer
reg [15:0] dest; // 16 bit destination read buffer
// ------- wire up the blitter subcomponent combinatorics --------
wire [15:0] src_skewed;
wire [15:0] src_halftoned;
wire [15:0] result;
// select current halftone line
wire [15:0] halftone_line = halftone_ram[smudge?src_skewed[3:0]:line_number];
wire skip_src_read = (no_src_hop || no_src_op) && !smudge;
wire no_src_hop; // hop doesn't require source read
wire no_src_op; // op -"-
wire no_dest_op; // op doesn't require dest read
// shift/select 16 bits of source
shift shift (
.skew (skew),
.in (src),
.out (src_skewed)
);
// apply halftone operation
halftone_op halftone_op (
.op (hop),
.in0 (halftone_line),
.in1 (src_skewed),
.no_src (no_src_hop),
.out (src_halftoned)
);
// apply blitter operation
blitter_op blitter_op (
.op (op),
.in0 (src_halftoned),
.in1 (dest),
.no_src (no_src_op),
.no_dest (no_dest_op),
.out (result)
);
// check if current column is first or last word in the row
wire first_word_in_row = (x_count == x_count_latch);
wire last_word_in_row = (x_count == 16'h0001);
// check if next column is first or last word in the row
wire next_is_first_word_in_row = (x_count_next == x_count_latch);
wire next_is_last_word_in_row = (x_count_next == 16'h0001);
// check if the current mask requires to read the destination first
wire mask_requires_dest =
first_word_in_row?(endmask1 != 16'hffff):
(last_word_in_row?(endmask3 != 16'hffff):
(endmask2 != 16'hffff));
// check if the next words mask requires to read the destination first
wire next_mask_requires_dest =
next_is_first_word_in_row?(endmask1 != 16'hffff):
(next_is_last_word_in_row?(endmask3 != 16'hffff):
(endmask2 != 16'hffff));
// the requirement to read the destination first may either come from the
// operation or from the fact that masking takes place
wire dest_required = mask_requires_dest || !no_dest_op;
wire next_dest_required = next_mask_requires_dest || !no_dest_op;
// apply masks
masking masking (
.endmask1 (endmask1),
.endmask2 (endmask2),
.endmask3 (endmask3),
.first (first_word_in_row),
.last (last_word_in_row),
.in0 (result),
.in1 (dest),
.out (bm_data_out)
);
endmodule // blitter
// the blitter operations
module blitter_op (
input [3:0] op,
input [15:0] in0,
input [15:0] in1,
output reg no_src,
output reg no_dest,
output reg [15:0] out
);
always @(op, in0, in1) begin
// return 1 for all ops that don't use in0 (src)
no_src = (op == 0) || (op == 5) || (op == 10) || (op == 15);
no_dest = (op == 0) || (op == 3) || (op == 12) || (op == 15);
case(op)
0: out = 16'h0000;
1: out = in0 & in1;
2: out = in0 & ~in1;
3: out = in0;
4: out = ~in0 & in1;
5: out = in1;
6: out = in0 ^ in1;
7: out = in0 | in1;
8: out = ~in0 & ~in1;
9: out = ~in0 ^ in1;
10: out = ~in1;
11: out = in0 | ~in1;
12: out = ~in0;
13: out = ~in0 | in1;
14: out = ~in0 | ~in1;
15: out = 16'hffff;
endcase; // case (op)
end
endmodule // blitter_op
// the blitter barrel shifter
module shift (
input [3:0] skew,
input [31:0] in,
output reg [15:0] out
);
always @(skew, in) begin
out = 16'h0000;
case(skew)
0: out = in[15:0];
1: out = in[16:1];
2: out = in[17:2];
3: out = in[18:3];
4: out = in[19:4];
5: out = in[20:5];
6: out = in[21:6];
7: out = in[22:7];
8: out = in[23:8];
9: out = in[24:9];
10: out = in[25:10];
11: out = in[26:11];
12: out = in[27:12];
13: out = in[28:13];
14: out = in[29:14];
15: out = in[30:15];
endcase; // case (skew)
end
endmodule // shift
// the halftone operations
module halftone_op (
input [1:0] op,
input [15:0] in0,
input [15:0] in1,
output reg no_src,
output reg [15:0] out
);
always @(op, in0, in1) begin
// return 1 for all ops that don't use in1 (src)
no_src = (op == 0) || (op == 1);
case(op)
0: out = 16'hffff;
1: out = in0;
2: out = in1;
3: out = in0 & in1;
endcase; // case (op)
end
endmodule // halftone_op
// masking
module masking (
input [15:0] endmask1,
input [15:0] endmask2,
input [15:0] endmask3,
input first,
input last,
input [15:0] in0,
input [15:0] in1,
output reg [15:0] out
);
always @(endmask1, endmask2, endmask3, first, last, in0, in1) begin
// neither first nor last: endmask2
out = (in0 & endmask2) | (in1 & ~endmask2);
// first (last may also be applied): endmask1
if(first) out = (in0 & endmask1) | (in1 & ~endmask1);
// last and not first: endmask3
else if(last) out = (in0 & endmask3) | (in1 & ~endmask3);
end
endmodule // masking