Skip to content

Instantly share code, notes, and snippets.

@sam-falvo
Created July 16, 2016 21:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sam-falvo/71139ddfc4e9b80c47e3fcce18e1f500 to your computer and use it in GitHub Desktop.
Save sam-falvo/71139ddfc4e9b80c47e3fcce18e1f500 to your computer and use it in GitHub Desktop.
`timescale 1ns / 1ps
// The fetch module is responsible for managing instruction fetch operations.
// This implies it must also deal with external interrupts as well as
// internally generated traps.
module fetch(
input reset_i,
input clk_i,
output [63:0] adr_o,
input ack_i,
input defined_i,
input pause_i,
output [1:0] size_o
);
// The ADR_O bus is used to address external memory.
// It's a full, 64-bit bus instead of a 63-bit bus, like what you'd
// expect from a Wishbone bus. This actually simplifies the control
// logic as well as the external Wishbone bridge.
wire [63:0] adr_o;
// We multiplex all the different sources of addresses onto this
// address bus. Sometimes, we'll need to pad bits to make things
// line up right.
assign adr_o =
(ADR_NPC) ? {r_npc, 2'b00} :
(ADR_NPCp2) ? {r_npc, 2'b10} :
64'd0;
// The instruction fetcher needs to know from where to fetch the next
// instruction. This is the purpose of the NPC register.
reg [63:2] r_npc;
// The SIZE_O bus indicates how big the transfer size is, and
// how many data bits of the input data bus are going to be used.
// The meanings are:
//
// 00 No bus cycle in progress.
// 01 One byte is expected to appear on DAT_I[7:0]
// 10 Two bytes are expected to appear on DAT_I[15:0]
// 11 Unused; must never appear.
reg [1:0] size_o;
// The instruction fetcher is a self-contained state machine.
// Some of the bits that determines the current state comes from
// a register called state. Others are taken as inputs from other
// parts of the computer. At present, there are 13 different states.
reg [2:0] state;
wire s0 = state_o == 3'b000;
wire s1 = state_o == 3'b001;
wire s2 = state_o == 3'b010;
wire s3 = state_o == 3'b011;
wire s4 = state_o == 3'b100;
wire s5 = state_o == 3'b101;
wire fire0 = (~reset_i) & (~defined_i) & s0;
wire fire1 = (~reset_i) & defined_i & pause_i & s0;
wire fire2 = (~reset_i) & defined_i & (~pause_i) & s0;
wire fire3 = (~reset_i) & (~ack_i) & s1;
wire fire4 = (~reset_i) & ack_i & s1;
wire fire5 = (~reset_i) & s2;
wire fire6 = (~reset_i) & (~ack_i) & s3;
wire fire7 = (~reset_i) & (~pause_i) & ack_i & s3;
wire fire8 = (~reset_i) & pause_i & ack_i & s3;
wire fire9 = (~reset_i) & pause_i & s4;
wire fire10 = (~reset_i) & (~pause_i) & s4;
wire fire11 = (~reset_i) & pause_i & s5;
wire fire12 = (~reset_i) & (~pause_i) & s5;
// Depending on which state case is firing at the moment,
// we want to set our next state in a sensible way.
wire [2:0] next_state =
(fire0 | fire2 | fire3) ? 1 :
(fire4) ? 2 :
(fire5 | fire6) ? 3 :
(fire8 | fire9) ? 4 :
(fire1 | fire11) ? 5 :
0;
always @(posedge clk_i or negedge clk_i) begin
$display("C%d ST=%d NS=%d F=%12b R%d D%d P%d A%d", clk_i, state, next_state, {fire0, fire1, fire2, fire3, fire4,fire5,fire6,fire7,fire8,fire9,fire10,fire11,fire12}, reset_i, defined_i, pause_i, ack_i);
end
// We only want to apply the low halfword instruction address when
// fetching the low halfword of the next opcode. Similarly, we
// only want to ask for the high halfword when we're ready.
wire ADR_NPC = fire2 | fire3 | fire4;
wire ADR_NPCp2 = |{fire5, fire6, fire7, fire8};
wire is_opcode_fetch = |{fire0, fire2, fire3, fire4, fire5, fire6, fire7, fire8};
wire SIZE_2 = is_opcode_fetch;
wire VPA_O = is_opcode_fetch;
// If we hit an undefined instruction, we want to take the exception.
wire ADR_MTVEC = fire0;
wire NPC_MTVEC = fire0;
// Whenever we handle an exception, we must set MEPC to either the CPC
// or to the NPC, depending on the nature of the exception.
wire MEPC_CPC = fire0;
// For every exception, we enter machine-mode, and that means turning
// off machine-mode interrupts. We also need to preserve the previous
// setting.
wire MPIE_MIE = fire0;
wire MIE_0 = fire0;
// Every exception has a cause. This logic figures out what it is.
wire MCAUSE_2 = fire0;
// We fetch instructions in two halfwords, to accomodate the 16-bit
// external bus.
reg [15:0] irl;
reg [15:0] irh;
// We trigger updates to IRL and IRH only at the appropriate times.
// Note that we can also optimize out one cycle if we recognize when
// we can bypass the loading of IRH.
wire IRL_DAT = fire4;
wire IRH_DAT = fire7 | fire8;
wire IR_DAT_IRL = fire7 | fire10;
wire CPC_NPC = IR_DAT_IRL;
wire NPC_NPCp4 = CPC_NPC;
always @(posedge clk_i) begin
if(reset_i) begin
// Upon hard reset,
// the NPC assumes the value $FFFFFFFFFFFFFF00.
r_npc <= 62'h3FFFFFFFFFFFFFC0;
// When we're in hard reset, all bus activity must
// come to a close.
size_o <= 2'b00;
end
// Upon every clock, we want to advance to the next state.
state <= next_state;
end
endmodule
// Exercise the instruction fetch module.
module test_fetch();
reg [15:0] story_o;
reg ack_o;
reg clk_o;
reg defined_o;
reg pause_o;
reg reset_o;
wire [1:0] size_i;
wire [63:0] adr_i;
fetch f(
.ack_i(ack_o),
.adr_o(adr_i),
.clk_i(clk_o),
.reset_i(reset_o),
.size_o(size_i),
.defined_i(defined_o),
.pause_i(pause_o)
);
always begin
#20 clk_o <= ~clk_o;
end
task tick;
input [15:0] story;
begin
story_o <= story;
wait(clk_o); wait(~clk_o);
end
endtask
task assert_adr;
input [63:0] expected;
begin
if(adr_i !== expected) begin
$display("@E %04X ADR_O Expected=%016X Got=%016X", story_o, expected, adr_i);
$stop;
end
end
endtask
task assert_size;
input [1:0] expected;
begin
if(size_i !== expected) begin
$display("@E %04X SIZE_O Expected=%016X Got=%016X", story_o, expected, size_i);
$stop;
end
end
endtask
initial begin
clk_o <= 0;
reset_o <= 0;
ack_o <= 0;
defined_o <= 1;
pause_o <= 0;
tick(16'hFFFF);
// When the CPU is reset, the bus must be idle.
reset_o <= 1;
tick(16'h0000);
assert_size(0);
tick(16'h0001);
assert_size(0);
// When the CPU comes out of reset, we expect the instruction at $FFFFFFFFFFFFFF00 to be fetched.
// This will take a total of four clock cycles, assuming ACK_I is asserted the whole time.
reset_o <= 0;
ack_o <= 1;
tick(16'h0100);
assert_adr(64'hFFFFFFFFFFFFFF00);
assert_size(2);
tick(16'h0110);
assert_adr(64'hFFFFFFFFFFFFFF02);
assert_size(2);
$display("@DONE");
$stop;
end
endmodule
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment